Merge remote-tracking branch 'origin/feat/exchange-proxy/post-cd-audit' into fix/ep/meta-transactions

This commit is contained in:
Lawrence Forman
2020-09-01 09:48:17 -04:00
29 changed files with 547 additions and 74 deletions

View File

@@ -101,6 +101,7 @@
"@0x/contracts-multisig": "^4.1.7", "@0x/contracts-multisig": "^4.1.7",
"@0x/contracts-staking": "^2.0.14", "@0x/contracts-staking": "^2.0.14",
"@0x/contracts-test-utils": "^5.3.4", "@0x/contracts-test-utils": "^5.3.4",
"@0x/contracts-zero-ex": "^0.2.0",
"@0x/subproviders": "^6.1.1", "@0x/subproviders": "^6.1.1",
"@0x/types": "^3.2.0", "@0x/types": "^3.2.0",
"@0x/typescript-typings": "^5.1.1", "@0x/typescript-typings": "^5.1.1",

View 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',
);
});
});

View File

@@ -21,6 +21,14 @@
{ {
"note": "Fix `TransformerDeployer.kill()` calling the wrong `die()` interface.", "note": "Fix `TransformerDeployer.kill()` calling the wrong `die()` interface.",
"pr": 2624 "pr": 2624
},
{
"note": "Address CD post-audit feedback",
"pr": 2657
},
{
"note": "Add `LogMetadataTransformer`",
"pr": 2657
} }
] ]
}, },

View File

@@ -77,6 +77,7 @@ contract Bootstrap is
/// @dev Self-destructs this contract. /// @dev Self-destructs this contract.
/// Can only be called by the deployer. /// Can only be called by the deployer.
function die() external { function die() external {
assert(address(this) == _implementation);
if (msg.sender != _deployer) { if (msg.sender != _deployer) {
LibProxyRichErrors.InvalidDieCallerError(msg.sender, _deployer).rrevert(); LibProxyRichErrors.InvalidDieCallerError(msg.sender, _deployer).rrevert();
} }

View File

@@ -271,7 +271,9 @@ contract MetaTransactions is
_validateMetaTransaction(state); _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() LibMetaTransactionsStorage.getStorage()
.mtxHashToExecutedBlockNumber[state.hash] = block.number; .mtxHashToExecutedBlockNumber[state.hash] = block.number;

View File

@@ -44,10 +44,6 @@ contract Ownable is
using LibRichErrorsV06 for bytes; 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) /// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
/// to allow the bootstrappers to call `extend()`. Ownership should be /// to allow the bootstrappers to call `extend()`. Ownership should be
/// transferred to the real owner by the bootstrapper after /// transferred to the real owner by the bootstrapper after

View File

@@ -49,10 +49,6 @@ contract SignatureValidator is
/// @dev Version of this feature. /// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); 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. /// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`. /// Should be delegatecalled by `Migrate.migrate()`.
/// @return success `LibMigrate.SUCCESS` on success. /// @return success `LibMigrate.SUCCESS` on success.

View File

@@ -42,10 +42,6 @@ contract SimpleFunctionRegistry is
using LibRichErrorsV06 for bytes; using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initializes this feature, registering its own functions. /// @dev Initializes this feature, registering its own functions.
/// @return success Magic bytes if successful. /// @return success Magic bytes if successful.
function bootstrap() function bootstrap()

View File

@@ -48,10 +48,6 @@ contract TokenSpender is
using LibRichErrorsV06 for bytes; using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initialize and register this feature. Should be delegatecalled /// @dev Initialize and register this feature. Should be delegatecalled
/// into during a `Migrate.migrate()`. /// into during a `Migrate.migrate()`.
/// @param allowanceTarget An `allowanceTarget` instance, configured to have /// @param allowanceTarget An `allowanceTarget` instance, configured to have

View File

@@ -61,10 +61,6 @@ contract TransformERC20 is
/// @dev Version of this feature. /// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0); 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. /// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`. /// Should be delegatecalled by `Migrate.migrate()`.
/// @param transformerDeployer The trusted deployer for transformers. /// @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. // Compute how much output token has been transferred to the taker.
state.takerOutputTokenBalanceAfter = state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker); LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) { if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
} else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError( LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(args.outputToken), address(args.outputToken),
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert(); ).rrevert();
} }
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
// Ensure enough output token has been sent to the taker. // Ensure enough output token has been sent to the taker.
if (outputTokenAmount < args.minOutputTokenAmount) { if (outputTokenAmount < args.minOutputTokenAmount) {
LibTransformERC20RichErrors.IncompleteTransformERC20Error( LibTransformERC20RichErrors.IncompleteTransformERC20Error(

View File

@@ -32,7 +32,6 @@ import "./LibERC20Transformer.sol";
contract AffiliateFeeTransformer is contract AffiliateFeeTransformer is
Transformer Transformer
{ {
// solhint-disable no-empty-blocks
using LibRichErrorsV06 for bytes; using LibRichErrorsV06 for bytes;
using LibSafeMathV06 for uint256; using LibSafeMathV06 for uint256;
using LibERC20Transformer for IERC20TokenV06; using LibERC20Transformer for IERC20TokenV06;
@@ -51,12 +50,6 @@ contract AffiliateFeeTransformer is
uint256 private constant MAX_UINT256 = uint256(-1); uint256 private constant MAX_UINT256 = uint256(-1);
/// @dev Create this contract.
constructor()
public
Transformer()
{}
/// @dev Transfers tokens to recipients. /// @dev Transfers tokens to recipients.
/// @param context Context information. /// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`). /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).

View File

@@ -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;
}
}

View File

@@ -39,9 +39,9 @@
"publish:private": "yarn build && gitpkg publish" "publish:private": "yarn build && gitpkg publish"
}, },
"config": { "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: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": { "repository": {
"type": "git", "type": "git",

View File

@@ -18,6 +18,7 @@ import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunction
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json'; import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json'; import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
import * as IZeroEx from '../generated-artifacts/IZeroEx.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 MetaTransactions from '../generated-artifacts/MetaTransactions.json';
import * as Ownable from '../generated-artifacts/Ownable.json'; import * as Ownable from '../generated-artifacts/Ownable.json';
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json'; import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
@@ -50,4 +51,5 @@ export const artifacts = {
SignatureValidator: SignatureValidator as ContractArtifact, SignatureValidator: SignatureValidator as ContractArtifact,
MetaTransactions: MetaTransactions as ContractArtifact, MetaTransactions: MetaTransactions as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact, BridgeAdapter: BridgeAdapter as ContractArtifact,
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
}; };

View File

@@ -16,6 +16,7 @@ export * from '../generated-wrappers/i_token_spender';
export * from '../generated-wrappers/i_transform_erc20'; export * from '../generated-wrappers/i_transform_erc20';
export * from '../generated-wrappers/i_zero_ex'; export * from '../generated-wrappers/i_zero_ex';
export * from '../generated-wrappers/initial_migration'; export * from '../generated-wrappers/initial_migration';
export * from '../generated-wrappers/log_metadata_transformer';
export * from '../generated-wrappers/meta_transactions'; export * from '../generated-wrappers/meta_transactions';
export * from '../generated-wrappers/ownable'; export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/pay_taker_transformer'; export * from '../generated-wrappers/pay_taker_transformer';

View File

@@ -12,6 +12,7 @@ import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json'; import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json'; import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.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 FlashWallet from '../test/generated-artifacts/FlashWallet.json';
import * as FullMigration from '../test/generated-artifacts/FullMigration.json'; import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.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 LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.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 MetaTransactions from '../test/generated-artifacts/MetaTransactions.json';
import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapterAddresses.json'; import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapterAddresses.json';
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json'; import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
@@ -141,6 +143,7 @@ export const artifacts = {
LibSignedCallData: LibSignedCallData as ContractArtifact, LibSignedCallData: LibSignedCallData as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact, FixinCommon: FixinCommon as ContractArtifact,
FixinEIP712: FixinEIP712 as ContractArtifact, FixinEIP712: FixinEIP712 as ContractArtifact,
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
FullMigration: FullMigration as ContractArtifact, FullMigration: FullMigration as ContractArtifact,
InitialMigration: InitialMigration as ContractArtifact, InitialMigration: InitialMigration as ContractArtifact,
LibBootstrap: LibBootstrap as ContractArtifact, LibBootstrap: LibBootstrap as ContractArtifact,
@@ -157,6 +160,7 @@ export const artifacts = {
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact, FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
IERC20Transformer: IERC20Transformer as ContractArtifact, IERC20Transformer: IERC20Transformer as ContractArtifact,
LibERC20Transformer: LibERC20Transformer as ContractArtifact, LibERC20Transformer: LibERC20Transformer as ContractArtifact,
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
PayTakerTransformer: PayTakerTransformer as ContractArtifact, PayTakerTransformer: PayTakerTransformer as ContractArtifact,
Transformer: Transformer as ContractArtifact, Transformer: Transformer as ContractArtifact,
WethTransformer: WethTransformer as ContractArtifact, WethTransformer: WethTransformer as ContractArtifact,

View File

@@ -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()', () => { describe('batchExecuteMetaTransactions()', () => {

View File

@@ -10,6 +10,7 @@ export * from '../test/generated-wrappers/bridge_adapter';
export * from '../test/generated-wrappers/fill_quote_transformer'; export * from '../test/generated-wrappers/fill_quote_transformer';
export * from '../test/generated-wrappers/fixin_common'; export * from '../test/generated-wrappers/fixin_common';
export * from '../test/generated-wrappers/fixin_e_i_p712'; 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/flash_wallet';
export * from '../test/generated-wrappers/full_migration'; export * from '../test/generated-wrappers/full_migration';
export * from '../test/generated-wrappers/i_allowance_target'; 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_rich_errors';
export * from '../test/generated-wrappers/lib_transform_erc20_storage'; export * from '../test/generated-wrappers/lib_transform_erc20_storage';
export * from '../test/generated-wrappers/lib_wallet_rich_errors'; 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/meta_transactions';
export * from '../test/generated-wrappers/mixin_adapter_addresses'; export * from '../test/generated-wrappers/mixin_adapter_addresses';
export * from '../test/generated-wrappers/mixin_balancer'; export * from '../test/generated-wrappers/mixin_balancer';

View File

@@ -16,6 +16,7 @@
"generated-artifacts/ITransformERC20.json", "generated-artifacts/ITransformERC20.json",
"generated-artifacts/IZeroEx.json", "generated-artifacts/IZeroEx.json",
"generated-artifacts/InitialMigration.json", "generated-artifacts/InitialMigration.json",
"generated-artifacts/LogMetadataTransformer.json",
"generated-artifacts/MetaTransactions.json", "generated-artifacts/MetaTransactions.json",
"generated-artifacts/Ownable.json", "generated-artifacts/Ownable.json",
"generated-artifacts/PayTakerTransformer.json", "generated-artifacts/PayTakerTransformer.json",
@@ -74,6 +75,7 @@
"test/generated-artifacts/LibTransformERC20RichErrors.json", "test/generated-artifacts/LibTransformERC20RichErrors.json",
"test/generated-artifacts/LibTransformERC20Storage.json", "test/generated-artifacts/LibTransformERC20Storage.json",
"test/generated-artifacts/LibWalletRichErrors.json", "test/generated-artifacts/LibWalletRichErrors.json",
"test/generated-artifacts/LogMetadataTransformer.json",
"test/generated-artifacts/MetaTransactions.json", "test/generated-artifacts/MetaTransactions.json",
"test/generated-artifacts/MixinAdapterAddresses.json", "test/generated-artifacts/MixinAdapterAddresses.json",
"test/generated-artifacts/MixinBalancer.json", "test/generated-artifacts/MixinBalancer.json",

View File

@@ -7,10 +7,10 @@ import {
encodeWethTransformerData, encodeWethTransformerData,
ETH_TOKEN_ADDRESS, ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide, FillQuoteTransformerSide,
findTransformerNonce,
} from '@0x/order-utils'; } from '@0x/order-utils';
import { BigNumber, providerUtils } from '@0x/utils'; import { BigNumber, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper'; import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import * as ethjs from 'ethereumjs-util';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { constants } from '../constants'; import { constants } from '../constants';
@@ -32,7 +32,6 @@ import { getTokenFromAssetData } from '../utils/utils';
// tslint:disable-next-line:custom-no-magic-numbers // tslint:disable-next-line:custom-no-magic-numbers
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
const { NULL_ADDRESS } = constants; const { NULL_ADDRESS } = constants;
const MAX_NONCE_GUESSES = 2048;
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
@@ -230,32 +229,3 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote { function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy; 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),
);
}

View File

@@ -8,6 +8,7 @@ import {
decodeWethTransformerData, decodeWethTransformerData,
ETH_TOKEN_ADDRESS, ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide, FillQuoteTransformerSide,
getTransformerAddress,
} from '@0x/order-utils'; } from '@0x/order-utils';
import { Order } from '@0x/types'; import { Order } from '@0x/types';
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils'; import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
@@ -16,10 +17,7 @@ import * as _ from 'lodash';
import 'mocha'; import 'mocha';
import { constants } from '../src/constants'; import { constants } from '../src/constants';
import { import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
ExchangeProxySwapQuoteConsumer,
getTransformerAddress,
} from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types'; import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types'; import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';

View File

@@ -1,4 +1,13 @@
[ [
{
"version": "5.2.0",
"changes": [
{
"note": "Add `exchangeProxyMetaTransactionSchema`",
"pr": 2657
}
]
},
{ {
"version": "5.1.0", "version": "5.1.0",
"changes": [ "changes": [

View File

@@ -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"
}

View File

@@ -7,6 +7,7 @@ import * as ecSignatureParameterSchema from '../schemas/ec_signature_parameter_s
import * as ecSignatureSchema from '../schemas/ec_signature_schema.json'; import * as ecSignatureSchema from '../schemas/ec_signature_schema.json';
import * as eip712DomainSchema from '../schemas/eip712_domain_schema.json'; import * as eip712DomainSchema from '../schemas/eip712_domain_schema.json';
import * as eip712TypedDataSchema from '../schemas/eip712_typed_data_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 hexSchema from '../schemas/hex_schema.json';
import * as indexFilterValuesSchema from '../schemas/index_filter_values_schema.json'; import * as indexFilterValuesSchema from '../schemas/index_filter_values_schema.json';
import * as jsNumber from '../schemas/js_number_schema.json'; import * as jsNumber from '../schemas/js_number_schema.json';
@@ -87,5 +88,6 @@ export const schemas = {
relayerApiOrdersResponseSchema, relayerApiOrdersResponseSchema,
relayerApiAssetDataPairsSchema, relayerApiAssetDataPairsSchema,
zeroExTransactionSchema, zeroExTransactionSchema,
exchangeProxyMetaTransactionSchema,
wholeNumberSchema, wholeNumberSchema,
}; };

View File

@@ -50,6 +50,7 @@
"./schemas/orderbook_request_schema.json", "./schemas/orderbook_request_schema.json",
"./schemas/orders_request_opts_schema.json", "./schemas/orders_request_opts_schema.json",
"./schemas/paged_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"
] ]
} }

View File

@@ -13,6 +13,14 @@
{ {
"note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.", "note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.",
"pr": 2657 "pr": 2657
},
{
"note": "Add `findTransformerNonce()` and `getTransformerAddress()` functions.",
"pr": 2657
},
{
"note": "Fix EP signature utils schema assertion.",
"pr": 2657
} }
] ]
}, },

View File

@@ -77,7 +77,9 @@ export {
AffiliateFeeTransformerData, AffiliateFeeTransformerData,
encodeAffiliateFeeTransformerData, encodeAffiliateFeeTransformerData,
decodeAffiliateFeeTransformerData, decodeAffiliateFeeTransformerData,
} from './transformer_data_encoders'; findTransformerNonce,
getTransformerAddress,
} from './transformer_utils';
export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils'; export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils';

View File

@@ -206,7 +206,9 @@ export const signatureUtils = {
transaction: ExchangeProxyMetaTransaction, transaction: ExchangeProxyMetaTransaction,
signerAddress: string, signerAddress: string,
): Promise<SignedExchangeProxyMetaTransaction> { ): Promise<SignedExchangeProxyMetaTransaction> {
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]); assert.doesConformToSchema('transaction', transaction, schemas.exchangeProxyMetaTransactionSchema, [
schemas.hexSchema,
]);
try { try {
const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync( const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
supportedProvider, supportedProvider,
@@ -253,7 +255,9 @@ export const signatureUtils = {
): Promise<SignedExchangeProxyMetaTransaction> { ): Promise<SignedExchangeProxyMetaTransaction> {
const provider = providerUtils.standardizeOrThrow(supportedProvider); const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isETHAddressHex('signerAddress', signerAddress); 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); const web3Wrapper = new Web3Wrapper(provider);
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
const normalizedSignerAddress = signerAddress.toLowerCase(); const normalizedSignerAddress = signerAddress.toLowerCase();

View File

@@ -1,5 +1,10 @@
import { Order } from '@0x/types'; import { Order } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils'; import { AbiEncoder, BigNumber } from '@0x/utils';
import * as ethjs from 'ethereumjs-util';
import { constants } from './constants';
const { NULL_ADDRESS } = constants;
const ORDER_ABI_COMPONENTS = [ const ORDER_ABI_COMPONENTS = [
{ name: 'makerAddress', type: 'address' }, { name: 'makerAddress', type: 'address' },
@@ -187,3 +192,36 @@ export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerD
export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData { export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData {
return affiliateFeeTransformerDataEncoder.decode(encoded); 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),
);
}