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-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",
|
||||||
|
|||||||
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.",
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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`).
|
||||||
|
|||||||
@@ -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"
|
"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",
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()', () => {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "5.2.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `exchangeProxyMetaTransactionSchema`",
|
||||||
|
"pr": 2657
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"changes": [
|
"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 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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user