Merge remote-tracking branch 'origin/feat/exchange-proxy/post-cd-audit' into fix/ep/meta-transactions
This commit is contained in:
@@ -101,6 +101,7 @@
|
||||
"@0x/contracts-multisig": "^4.1.7",
|
||||
"@0x/contracts-staking": "^2.0.14",
|
||||
"@0x/contracts-test-utils": "^5.3.4",
|
||||
"@0x/contracts-zero-ex": "^0.2.0",
|
||||
"@0x/subproviders": "^6.1.1",
|
||||
"@0x/types": "^3.2.0",
|
||||
"@0x/typescript-typings": "^5.1.1",
|
||||
|
304
contracts/integrations/test/exchange-proxy/mtx_test.ts
Normal file
304
contracts/integrations/test/exchange-proxy/mtx_test.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { IExchangeContract } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, getRandomPortion, verifyEventsFromLogs } from '@0x/contracts-test-utils';
|
||||
import {
|
||||
artifacts as exchangeProxyArtifacts,
|
||||
IZeroExContract,
|
||||
LogMetadataTransformerContract,
|
||||
signCallData,
|
||||
} from '@0x/contracts-zero-ex';
|
||||
import { migrateOnceAsync } from '@0x/migrations';
|
||||
import {
|
||||
assetDataUtils,
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
findTransformerNonce,
|
||||
signatureUtils,
|
||||
SignedExchangeProxyMetaTransaction,
|
||||
} from '@0x/order-utils';
|
||||
import { AssetProxyId, Order, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, NULL_BYTES32, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets.only('exchange proxy - meta-transactions', env => {
|
||||
const quoteSignerKey = hexUtils.random();
|
||||
const quoteSigner = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(quoteSignerKey)));
|
||||
let owner: string;
|
||||
let relayer: string;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let flashWalletAddress: string;
|
||||
let zeroEx: IZeroExContract;
|
||||
let exchange: IExchangeContract;
|
||||
let inputToken: DummyERC20TokenContract;
|
||||
let outputToken: DummyERC20TokenContract;
|
||||
let feeToken: DummyERC20TokenContract;
|
||||
let addresses: ContractAddresses;
|
||||
let protocolFee: BigNumber;
|
||||
let metadataTransformer: LogMetadataTransformerContract;
|
||||
const GAS_PRICE = new BigNumber('1e9');
|
||||
const MAKER_BALANCE = new BigNumber('100e18');
|
||||
const TAKER_BALANCE = new BigNumber('100e18');
|
||||
const TAKER_FEE_BALANCE = new BigNumber('100e18');
|
||||
|
||||
before(async () => {
|
||||
[, relayer, maker, taker] = await env.getAccountAddressesAsync();
|
||||
addresses = await migrateOnceAsync(env.provider);
|
||||
zeroEx = new IZeroExContract(addresses.exchangeProxy, env.provider, env.txDefaults, {
|
||||
LogMetadataTransformer: LogMetadataTransformerContract.ABI(),
|
||||
DummyERC20Token: DummyERC20TokenContract.ABI(),
|
||||
});
|
||||
exchange = new IExchangeContract(addresses.exchange, env.provider, env.txDefaults);
|
||||
[inputToken, outputToken, feeToken] = await Promise.all(
|
||||
[...new Array(3)].map(i =>
|
||||
DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
`DummyToken-${i}`,
|
||||
`TOK${i}`,
|
||||
new BigNumber(18),
|
||||
BigNumber.max(MAKER_BALANCE, TAKER_BALANCE),
|
||||
),
|
||||
),
|
||||
);
|
||||
// LogMetadataTransformer is not deployed in migrations.
|
||||
metadataTransformer = await LogMetadataTransformerContract.deployFrom0xArtifactAsync(
|
||||
exchangeProxyArtifacts.LogMetadataTransformer,
|
||||
env.provider,
|
||||
{
|
||||
...env.txDefaults,
|
||||
from: addresses.exchangeProxyTransformerDeployer,
|
||||
},
|
||||
{},
|
||||
);
|
||||
owner = await zeroEx.owner().callAsync();
|
||||
protocolFee = await exchange.protocolFeeMultiplier().callAsync();
|
||||
flashWalletAddress = await zeroEx.getTransformWallet().callAsync();
|
||||
const erc20Proxy = await exchange.getAssetProxy(AssetProxyId.ERC20).callAsync();
|
||||
const allowanceTarget = await zeroEx.getAllowanceTarget().callAsync();
|
||||
await outputToken.mint(MAKER_BALANCE).awaitTransactionSuccessAsync({ from: maker });
|
||||
await inputToken.mint(TAKER_BALANCE).awaitTransactionSuccessAsync({ from: taker });
|
||||
await feeToken.mint(TAKER_FEE_BALANCE).awaitTransactionSuccessAsync({ from: taker });
|
||||
await outputToken.approve(erc20Proxy, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker });
|
||||
await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
|
||||
await feeToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
|
||||
await zeroEx.setQuoteSigner(quoteSigner).awaitTransactionSuccessAsync({ from: owner });
|
||||
});
|
||||
|
||||
interface Transformation {
|
||||
deploymentNonce: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface SwapInfo {
|
||||
inputTokenAddress: string;
|
||||
outputTokenAddress: string;
|
||||
inputTokenAmount: BigNumber;
|
||||
minOutputTokenAmount: BigNumber;
|
||||
transformations: Transformation[];
|
||||
orders: SignedOrder[];
|
||||
}
|
||||
|
||||
async function generateSwapAsync(orderFields: Partial<Order> = {}): Promise<SwapInfo> {
|
||||
const order = await signatureUtils.ecSignTypedDataOrderAsync(
|
||||
env.provider,
|
||||
{
|
||||
chainId: 1337,
|
||||
exchangeAddress: exchange.address,
|
||||
expirationTimeSeconds: new BigNumber(Date.now()),
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
feeRecipientAddress: NULL_ADDRESS,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
takerAddress: flashWalletAddress,
|
||||
makerAddress: maker,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
|
||||
makerFeeAssetData: NULL_BYTES,
|
||||
takerFeeAssetData: NULL_BYTES,
|
||||
takerAssetAmount: getRandomPortion(TAKER_BALANCE),
|
||||
makerAssetAmount: getRandomPortion(MAKER_BALANCE),
|
||||
makerFee: ZERO_AMOUNT,
|
||||
takerFee: ZERO_AMOUNT,
|
||||
...orderFields,
|
||||
},
|
||||
maker,
|
||||
);
|
||||
const transformations = [
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
addresses.transformers.fillQuoteTransformer,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: encodeFillQuoteTransformerData({
|
||||
orders: [order],
|
||||
signatures: [order.signature],
|
||||
buyToken: outputToken.address,
|
||||
sellToken: inputToken.address,
|
||||
fillAmount: order.takerAssetAmount,
|
||||
maxOrderFillAmounts: [],
|
||||
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
}),
|
||||
},
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
addresses.transformers.payTakerTransformer,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [inputToken.address, outputToken.address, ETH_TOKEN_ADDRESS],
|
||||
amounts: [MAX_UINT256, MAX_UINT256, MAX_UINT256],
|
||||
}),
|
||||
},
|
||||
{
|
||||
deploymentNonce: findTransformerNonce(
|
||||
metadataTransformer.address,
|
||||
addresses.exchangeProxyTransformerDeployer,
|
||||
),
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
];
|
||||
return {
|
||||
transformations,
|
||||
orders: [order],
|
||||
inputTokenAddress: inputToken.address,
|
||||
outputTokenAddress: outputToken.address,
|
||||
inputTokenAmount: order.takerAssetAmount,
|
||||
minOutputTokenAmount: order.makerAssetAmount,
|
||||
};
|
||||
}
|
||||
|
||||
function getSwapData(swap: SwapInfo): string {
|
||||
return zeroEx
|
||||
.transformERC20(
|
||||
swap.inputTokenAddress,
|
||||
swap.outputTokenAddress,
|
||||
swap.inputTokenAmount,
|
||||
swap.minOutputTokenAmount,
|
||||
swap.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
|
||||
function getSignedSwapData(swap: SwapInfo, signerKey?: string): string {
|
||||
return signCallData(
|
||||
zeroEx
|
||||
.transformERC20(
|
||||
swap.inputTokenAddress,
|
||||
swap.outputTokenAddress,
|
||||
swap.inputTokenAmount,
|
||||
swap.minOutputTokenAmount,
|
||||
swap.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
signerKey ? signerKey : quoteSignerKey,
|
||||
);
|
||||
}
|
||||
|
||||
async function createMetaTransactionAsync(
|
||||
data: string,
|
||||
value: BigNumber,
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||
env.provider,
|
||||
{
|
||||
value,
|
||||
signer: taker,
|
||||
sender: relayer,
|
||||
minGasPrice: GAS_PRICE,
|
||||
maxGasPrice: GAS_PRICE,
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000) + 60),
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
callData: data,
|
||||
feeToken: feeToken.address,
|
||||
feeAmount: getRandomPortion(TAKER_FEE_BALANCE),
|
||||
domain: {
|
||||
chainId: 1,
|
||||
name: 'ZeroEx',
|
||||
version: '1.0.0',
|
||||
verifyingContract: zeroEx.address,
|
||||
},
|
||||
},
|
||||
taker,
|
||||
);
|
||||
}
|
||||
|
||||
it('can call `transformERC20()` with signed calldata and a relayer fee', async () => {
|
||||
const swap = await generateSwapAsync();
|
||||
const callDataHash = hexUtils.hash(getSwapData(swap));
|
||||
const signedSwapData = getSignedSwapData(swap);
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, mtx.signature)
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
callDataHash,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
|
||||
it('can call `transformERC20()` with wrongly signed calldata and a relayer fee', async () => {
|
||||
const swap = await generateSwapAsync();
|
||||
const signedSwapData = getSignedSwapData(swap, hexUtils.random());
|
||||
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
|
||||
const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee);
|
||||
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
|
||||
const receipt = await zeroEx
|
||||
.executeMetaTransaction(mtx, mtx.signature)
|
||||
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
|
||||
const relayerEthRefund = relayerEthBalanceBefore
|
||||
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
|
||||
.minus(GAS_PRICE.times(receipt.gasUsed));
|
||||
// Ensure the relayer got back the unused protocol fees.
|
||||
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
|
||||
// Ensure the relayer got paid mtx fees.
|
||||
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount);
|
||||
// Ensure the taker got output tokens.
|
||||
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
|
||||
// Ensure the maker got input tokens.
|
||||
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
|
||||
// Check events.
|
||||
verifyEventsFromLogs(
|
||||
receipt.logs,
|
||||
[
|
||||
{
|
||||
taker,
|
||||
// Only signed calldata should have a nonzero hash.
|
||||
callDataHash: NULL_BYTES32,
|
||||
sender: zeroEx.address,
|
||||
data: NULL_BYTES,
|
||||
},
|
||||
],
|
||||
'TransformerMetadata',
|
||||
);
|
||||
});
|
||||
});
|
@@ -21,6 +21,14 @@
|
||||
{
|
||||
"note": "Fix `TransformerDeployer.kill()` calling the wrong `die()` interface.",
|
||||
"pr": 2624
|
||||
},
|
||||
{
|
||||
"note": "Address CD post-audit feedback",
|
||||
"pr": 2657
|
||||
},
|
||||
{
|
||||
"note": "Add `LogMetadataTransformer`",
|
||||
"pr": 2657
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -77,6 +77,7 @@ contract Bootstrap is
|
||||
/// @dev Self-destructs this contract.
|
||||
/// Can only be called by the deployer.
|
||||
function die() external {
|
||||
assert(address(this) == _implementation);
|
||||
if (msg.sender != _deployer) {
|
||||
LibProxyRichErrors.InvalidDieCallerError(msg.sender, _deployer).rrevert();
|
||||
}
|
||||
|
@@ -271,7 +271,9 @@ contract MetaTransactions is
|
||||
|
||||
_validateMetaTransaction(state);
|
||||
|
||||
// Mark the transaction executed.
|
||||
// Mark the transaction executed by storing the block at which it was executed.
|
||||
// Currently the block number just indicates that the mtx was executed and
|
||||
// serves no other purpose from within this contract.
|
||||
LibMetaTransactionsStorage.getStorage()
|
||||
.mtxHashToExecutedBlockNumber[state.hash] = block.number;
|
||||
|
||||
|
@@ -44,10 +44,6 @@ contract Ownable is
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
|
||||
/// to allow the bootstrappers to call `extend()`. Ownership should be
|
||||
/// transferred to the real owner by the bootstrapper after
|
||||
|
@@ -49,10 +49,6 @@ contract SignatureValidator is
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @return success `LibMigrate.SUCCESS` on success.
|
||||
|
@@ -42,10 +42,6 @@ contract SimpleFunctionRegistry is
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initializes this feature, registering its own functions.
|
||||
/// @return success Magic bytes if successful.
|
||||
function bootstrap()
|
||||
|
@@ -48,10 +48,6 @@ contract TokenSpender is
|
||||
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature. Should be delegatecalled
|
||||
/// into during a `Migrate.migrate()`.
|
||||
/// @param allowanceTarget An `allowanceTarget` instance, configured to have
|
||||
|
@@ -61,10 +61,6 @@ contract TransformERC20 is
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
|
||||
|
||||
constructor() public FixinCommon() {
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
/// @param transformerDeployer The trusted deployer for transformers.
|
||||
@@ -257,16 +253,15 @@ contract TransformERC20 is
|
||||
// Compute how much output token has been transferred to the taker.
|
||||
state.takerOutputTokenBalanceAfter =
|
||||
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
|
||||
if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) {
|
||||
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
|
||||
state.takerOutputTokenBalanceBefore
|
||||
);
|
||||
} else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
|
||||
if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
|
||||
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
|
||||
address(args.outputToken),
|
||||
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
|
||||
).rrevert();
|
||||
}
|
||||
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
|
||||
state.takerOutputTokenBalanceBefore
|
||||
);
|
||||
// Ensure enough output token has been sent to the taker.
|
||||
if (outputTokenAmount < args.minOutputTokenAmount) {
|
||||
LibTransformERC20RichErrors.IncompleteTransformERC20Error(
|
||||
|
@@ -32,7 +32,6 @@ import "./LibERC20Transformer.sol";
|
||||
contract AffiliateFeeTransformer is
|
||||
Transformer
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
@@ -51,12 +50,6 @@ contract AffiliateFeeTransformer is
|
||||
|
||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
||||
|
||||
/// @dev Create this contract.
|
||||
constructor()
|
||||
public
|
||||
Transformer()
|
||||
{}
|
||||
|
||||
/// @dev Transfers tokens to recipients.
|
||||
/// @param context Context information.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
|
||||
|
||||
/// @dev A transformer that just emits an event with an arbitrary byte payload.
|
||||
contract LogMetadataTransformer is
|
||||
Transformer
|
||||
{
|
||||
event TransformerMetadata(bytes32 callDataHash, address sender, address taker, bytes data);
|
||||
|
||||
/// @dev Maximum uint256 value.
|
||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
||||
|
||||
/// @dev Emits an event.
|
||||
/// @param context Context information.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
function transform(TransformContext calldata context)
|
||||
external
|
||||
override
|
||||
returns (bytes4 success)
|
||||
{
|
||||
emit TransformerMetadata(context.callDataHash, context.sender, context.taker, context.data);
|
||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||
}
|
||||
}
|
@@ -39,9 +39,9 @@
|
||||
"publish:private": "yarn build && gitpkg publish"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions,BridgeAdapter",
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions,LogMetadataTransformer",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|MetaTransactions|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -18,6 +18,7 @@ import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunction
|
||||
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
|
||||
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
|
||||
import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
|
||||
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
|
||||
import * as MetaTransactions from '../generated-artifacts/MetaTransactions.json';
|
||||
import * as Ownable from '../generated-artifacts/Ownable.json';
|
||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||
@@ -50,4 +51,5 @@ export const artifacts = {
|
||||
SignatureValidator: SignatureValidator as ContractArtifact,
|
||||
MetaTransactions: MetaTransactions as ContractArtifact,
|
||||
BridgeAdapter: BridgeAdapter as ContractArtifact,
|
||||
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
||||
};
|
||||
|
@@ -16,6 +16,7 @@ export * from '../generated-wrappers/i_token_spender';
|
||||
export * from '../generated-wrappers/i_transform_erc20';
|
||||
export * from '../generated-wrappers/i_zero_ex';
|
||||
export * from '../generated-wrappers/initial_migration';
|
||||
export * from '../generated-wrappers/log_metadata_transformer';
|
||||
export * from '../generated-wrappers/meta_transactions';
|
||||
export * from '../generated-wrappers/ownable';
|
||||
export * from '../generated-wrappers/pay_taker_transformer';
|
||||
|
@@ -12,6 +12,7 @@ import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
|
||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
||||
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
|
||||
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json';
|
||||
import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentrancyGuard.json';
|
||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
||||
@@ -53,6 +54,7 @@ import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpe
|
||||
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
||||
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
||||
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
|
||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||
import * as MetaTransactions from '../test/generated-artifacts/MetaTransactions.json';
|
||||
import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapterAddresses.json';
|
||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||
@@ -141,6 +143,7 @@ export const artifacts = {
|
||||
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
||||
FixinCommon: FixinCommon as ContractArtifact,
|
||||
FixinEIP712: FixinEIP712 as ContractArtifact,
|
||||
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
|
||||
FullMigration: FullMigration as ContractArtifact,
|
||||
InitialMigration: InitialMigration as ContractArtifact,
|
||||
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||
@@ -157,6 +160,7 @@ export const artifacts = {
|
||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
Transformer: Transformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
|
@@ -539,6 +539,72 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reenter `executeMetaTransaction()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('executeMetaTransaction'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reenter `batchExecuteMetaTransactions()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('batchExecuteMetaTransactions'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchExecuteMetaTransactions()', () => {
|
||||
|
@@ -10,6 +10,7 @@ export * from '../test/generated-wrappers/bridge_adapter';
|
||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||
export * from '../test/generated-wrappers/fixin_common';
|
||||
export * from '../test/generated-wrappers/fixin_e_i_p712';
|
||||
export * from '../test/generated-wrappers/fixin_reentrancy_guard';
|
||||
export * from '../test/generated-wrappers/flash_wallet';
|
||||
export * from '../test/generated-wrappers/full_migration';
|
||||
export * from '../test/generated-wrappers/i_allowance_target';
|
||||
@@ -51,6 +52,7 @@ export * from '../test/generated-wrappers/lib_token_spender_storage';
|
||||
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
|
||||
export * from '../test/generated-wrappers/lib_wallet_rich_errors';
|
||||
export * from '../test/generated-wrappers/log_metadata_transformer';
|
||||
export * from '../test/generated-wrappers/meta_transactions';
|
||||
export * from '../test/generated-wrappers/mixin_adapter_addresses';
|
||||
export * from '../test/generated-wrappers/mixin_balancer';
|
||||
|
@@ -16,6 +16,7 @@
|
||||
"generated-artifacts/ITransformERC20.json",
|
||||
"generated-artifacts/IZeroEx.json",
|
||||
"generated-artifacts/InitialMigration.json",
|
||||
"generated-artifacts/LogMetadataTransformer.json",
|
||||
"generated-artifacts/MetaTransactions.json",
|
||||
"generated-artifacts/Ownable.json",
|
||||
"generated-artifacts/PayTakerTransformer.json",
|
||||
@@ -74,6 +75,7 @@
|
||||
"test/generated-artifacts/LibTransformERC20RichErrors.json",
|
||||
"test/generated-artifacts/LibTransformERC20Storage.json",
|
||||
"test/generated-artifacts/LibWalletRichErrors.json",
|
||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||
"test/generated-artifacts/MetaTransactions.json",
|
||||
"test/generated-artifacts/MixinAdapterAddresses.json",
|
||||
"test/generated-artifacts/MixinBalancer.json",
|
||||
|
@@ -7,10 +7,10 @@ import {
|
||||
encodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
findTransformerNonce,
|
||||
} from '@0x/order-utils';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
@@ -32,7 +32,6 @@ import { getTokenFromAssetData } from '../utils/utils';
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
||||
const { NULL_ADDRESS } = constants;
|
||||
const MAX_NONCE_GUESSES = 2048;
|
||||
|
||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
public readonly provider: ZeroExProvider;
|
||||
@@ -230,32 +229,3 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||
return quote.type === MarketOperation.Buy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nonce for a transformer given its deployer.
|
||||
* If `deployer` is the null address, zero will always be returned.
|
||||
*/
|
||||
export function findTransformerNonce(transformer: string, deployer: string = NULL_ADDRESS): number {
|
||||
if (deployer === NULL_ADDRESS) {
|
||||
return 0;
|
||||
}
|
||||
const lowercaseTransformer = transformer.toLowerCase();
|
||||
// Try to guess the nonce.
|
||||
for (let nonce = 0; nonce < MAX_NONCE_GUESSES; ++nonce) {
|
||||
const deployedAddress = getTransformerAddress(deployer, nonce);
|
||||
if (deployedAddress === lowercaseTransformer) {
|
||||
return nonce;
|
||||
}
|
||||
}
|
||||
throw new Error(`${deployer} did not deploy ${transformer}!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the deployed address for a transformer given a deployer and nonce.
|
||||
*/
|
||||
export function getTransformerAddress(deployer: string, nonce: number): string {
|
||||
return ethjs.bufferToHex(
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
ethjs.rlphash([deployer, nonce] as any).slice(12),
|
||||
);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
decodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
getTransformerAddress,
|
||||
} from '@0x/order-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
|
||||
@@ -16,10 +17,7 @@ import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import {
|
||||
ExchangeProxySwapQuoteConsumer,
|
||||
getTransformerAddress,
|
||||
} from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
|
||||
import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
|
||||
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||
import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "5.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `exchangeProxyMetaTransactionSchema`",
|
||||
"pr": 2657
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "5.1.0",
|
||||
"changes": [
|
||||
|
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"id": "/exchangeProxyMetaTransactionSchema",
|
||||
"properties": {
|
||||
"signer": { "$ref": "/addressSchema" },
|
||||
"sender": { "$ref": "/addressSchema" },
|
||||
"minGasPrice": { "$ref": "/wholeNumberSchema" },
|
||||
"maxGasPrice": { "$ref": "/wholeNumberSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" },
|
||||
"salt": { "$ref": "/wholeNumberSchema" },
|
||||
"callData": { "$ref": "/hexSchema" },
|
||||
"value": { "$ref": "/wholeNumberSchema" },
|
||||
"feeToken": { "$ref": "/addressSchema" },
|
||||
"feeAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"domain": { "$ref": "/eip712DomainSchema" }
|
||||
},
|
||||
"required": [
|
||||
"signer",
|
||||
"sender",
|
||||
"minGasPrice",
|
||||
"maxGasPrice",
|
||||
"expirationTimeSeconds",
|
||||
"salt",
|
||||
"callData",
|
||||
"value",
|
||||
"feeToken",
|
||||
"feeAmount",
|
||||
"domain"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
@@ -7,6 +7,7 @@ import * as ecSignatureParameterSchema from '../schemas/ec_signature_parameter_s
|
||||
import * as ecSignatureSchema from '../schemas/ec_signature_schema.json';
|
||||
import * as eip712DomainSchema from '../schemas/eip712_domain_schema.json';
|
||||
import * as eip712TypedDataSchema from '../schemas/eip712_typed_data_schema.json';
|
||||
import * as exchangeProxyMetaTransactionSchema from '../schemas/exchange_proxy_meta_transaction_schema.json';
|
||||
import * as hexSchema from '../schemas/hex_schema.json';
|
||||
import * as indexFilterValuesSchema from '../schemas/index_filter_values_schema.json';
|
||||
import * as jsNumber from '../schemas/js_number_schema.json';
|
||||
@@ -87,5 +88,6 @@ export const schemas = {
|
||||
relayerApiOrdersResponseSchema,
|
||||
relayerApiAssetDataPairsSchema,
|
||||
zeroExTransactionSchema,
|
||||
exchangeProxyMetaTransactionSchema,
|
||||
wholeNumberSchema,
|
||||
};
|
||||
|
@@ -50,6 +50,7 @@
|
||||
"./schemas/orderbook_request_schema.json",
|
||||
"./schemas/orders_request_opts_schema.json",
|
||||
"./schemas/paged_request_opts_schema.json",
|
||||
"./schemas/order_config_request_schema.json"
|
||||
"./schemas/order_config_request_schema.json",
|
||||
"./schemas/exchange_proxy_meta_transaction_schema.json"
|
||||
]
|
||||
}
|
||||
|
@@ -13,6 +13,14 @@
|
||||
{
|
||||
"note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.",
|
||||
"pr": 2657
|
||||
},
|
||||
{
|
||||
"note": "Add `findTransformerNonce()` and `getTransformerAddress()` functions.",
|
||||
"pr": 2657
|
||||
},
|
||||
{
|
||||
"note": "Fix EP signature utils schema assertion.",
|
||||
"pr": 2657
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -77,7 +77,9 @@ export {
|
||||
AffiliateFeeTransformerData,
|
||||
encodeAffiliateFeeTransformerData,
|
||||
decodeAffiliateFeeTransformerData,
|
||||
} from './transformer_data_encoders';
|
||||
findTransformerNonce,
|
||||
getTransformerAddress,
|
||||
} from './transformer_utils';
|
||||
|
||||
export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils';
|
||||
|
||||
|
@@ -206,7 +206,9 @@ export const signatureUtils = {
|
||||
transaction: ExchangeProxyMetaTransaction,
|
||||
signerAddress: string,
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.exchangeProxyMetaTransactionSchema, [
|
||||
schemas.hexSchema,
|
||||
]);
|
||||
try {
|
||||
const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
|
||||
supportedProvider,
|
||||
@@ -253,7 +255,9 @@ export const signatureUtils = {
|
||||
): Promise<SignedExchangeProxyMetaTransaction> {
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
|
||||
assert.doesConformToSchema('transaction', transaction, schemas.exchangeProxyMetaTransactionSchema, [
|
||||
schemas.hexSchema,
|
||||
]);
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||
|
@@ -1,5 +1,10 @@
|
||||
import { Order } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
const { NULL_ADDRESS } = constants;
|
||||
|
||||
const ORDER_ABI_COMPONENTS = [
|
||||
{ name: 'makerAddress', type: 'address' },
|
||||
@@ -187,3 +192,36 @@ export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerD
|
||||
export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData {
|
||||
return affiliateFeeTransformerDataEncoder.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nonce for a transformer given its deployer.
|
||||
* If `deployer` is the null address, zero will always be returned.
|
||||
*/
|
||||
export function findTransformerNonce(
|
||||
transformer: string,
|
||||
deployer: string = NULL_ADDRESS,
|
||||
maxGuesses: number = 1024,
|
||||
): number {
|
||||
if (deployer === NULL_ADDRESS) {
|
||||
return 0;
|
||||
}
|
||||
const lowercaseTransformer = transformer.toLowerCase();
|
||||
// Try to guess the nonce.
|
||||
for (let nonce = 0; nonce < maxGuesses; ++nonce) {
|
||||
const deployedAddress = getTransformerAddress(deployer, nonce);
|
||||
if (deployedAddress === lowercaseTransformer) {
|
||||
return nonce;
|
||||
}
|
||||
}
|
||||
throw new Error(`${deployer} did not deploy ${transformer}!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the deployed address for a transformer given a deployer and nonce.
|
||||
*/
|
||||
export function getTransformerAddress(deployer: string, nonce: number): string {
|
||||
return ethjs.bufferToHex(
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
ethjs.rlphash([deployer, nonce] as any).slice(12),
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user