Merge pull request #2657 from 0xProject/fix/ep/meta-transactions

EP: MetaTransactions fixes
This commit is contained in:
Lawrence Forman 2020-09-03 23:21:19 -04:00 committed by GitHub
commit 08ae43aad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
117 changed files with 2368 additions and 849 deletions

View File

@ -290,7 +290,7 @@ describe('AssetProxyDispatcher', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHash,
encodedAssetData,
nestedError.toString(),
nestedError.encode(),
);
const tx = assetProxyDispatcher
.dispatchTransferFrom(orderHash, encodedAssetData, makerAddress, takerAddress, amount)
@ -313,7 +313,7 @@ describe('AssetProxyDispatcher', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
transferIndexAsBytes32,
assetDataB,
nestedError.toString(),
nestedError.encode(),
);
const tx = assetProxyDispatcher
.simulateDispatchTransferFromCalls(

View File

@ -300,7 +300,7 @@ blockchainTests.resets('MixinSignatureValidator', env => {
hashHex,
validatorWallet.address,
signatureHex,
new StringRevertError(validatorWalletRevertReason).toString(),
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
@ -562,7 +562,7 @@ blockchainTests.resets('MixinSignatureValidator', env => {
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).toString(),
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
@ -693,7 +693,7 @@ blockchainTests.resets('MixinSignatureValidator', env => {
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).toString(),
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
@ -916,7 +916,7 @@ blockchainTests.resets('MixinSignatureValidator', env => {
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).toString(),
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
@ -1041,7 +1041,7 @@ blockchainTests.resets('MixinSignatureValidator', env => {
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).toString(),
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);

View File

@ -101,7 +101,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const executableError = new StringRevertError('EXECUTABLE_FAILED');
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHash,
executableError.toString(),
executableError.encode(),
);
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
@ -123,7 +123,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const executableError = new StringRevertError('EXECUTABLE_FAILED');
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHash,
executableError.toString(),
executableError.encode(),
);
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
@ -145,7 +145,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const executableError = new StringRevertError('EXECUTABLE_FAILED');
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHash,
executableError.toString(),
executableError.encode(),
);
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
@ -280,7 +280,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const outerExecuteTransactionHash = transactionHashUtils.getTransactionHashHex(outerExecuteTransaction);
const outerExpectedError = new ExchangeRevertErrors.TransactionExecutionError(
outerExecuteTransactionHash,
innerExpectedError.toString(),
innerExpectedError.encode(),
);
const tx = transactionsContract
.batchExecuteTransactions([outerExecuteTransaction], [randomSignature()])
@ -363,7 +363,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const errorData = new ExchangeRevertErrors.TransactionInvalidContextError(
innerTransactionHash,
accounts[0],
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(outerTransactionHash, errorData);
const tx = transactionsContract
.executeTransaction(outerTransaction, validSignature)
@ -385,7 +385,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const errorData = new ExchangeRevertErrors.TransactionInvalidContextError(
innerTransactionHash,
accounts[0],
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(outerTransactionHash, errorData);
const tx = transactionsContract
.executeTransaction(outerTransaction, validSignature)
@ -466,7 +466,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const executableError = new StringRevertError('EXECUTABLE_FAILED');
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHash,
executableError.toString(),
executableError.encode(),
);
const tx = transactionsContract
.executeTransaction(transaction, randomSignature())
@ -486,7 +486,7 @@ blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDef
const executableError = new StringRevertError('EXECUTABLE_FAILED');
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHash,
executableError.toString(),
executableError.encode(),
);
const tx = transactionsContract
.executeTransaction(transaction, validSignature)

View File

@ -5,6 +5,10 @@
{
"note": "Update curveBridge tests",
"pr": 2633
},
{
"note": "Add EP RFQT + MTX tests",
"pr": 2692
}
]
},

View File

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

View File

@ -0,0 +1,426 @@
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, ZeroExRevertErrors } from '@0x/utils';
import * as ethjs from 'ethereumjs-util';
const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, NULL_BYTES32, ZERO_AMOUNT } = constants;
blockchainTests.resets('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> = {}, isRfqt: boolean = false): 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: isRfqt ? flashWalletAddress : NULL_ADDRESS,
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.
rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS,
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,
fee?: BigNumber | number,
): 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: fee !== undefined ? new BigNumber(fee) : 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 no 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, 0);
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 no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// 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 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',
);
});
it('`transformERC20()` can fill RFQT order if calldata is signed', async () => {
const swap = await generateSwapAsync({}, true);
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, 0);
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 no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// 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('`transformERC20()` can fill RFQT order if calldata is not signed but no quote signer configured', async () => {
const swap = await generateSwapAsync({}, true);
const callData = getSwapData(swap);
const callDataHash = hexUtils.hash(callData);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(callData, _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
await zeroEx.setQuoteSigner(NULL_ADDRESS).awaitTransactionSuccessAsync({ from: owner });
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 no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// 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('`transformERC20()` cannot fill RFQT order if calldata is not signed', async () => {
const swap = await generateSwapAsync({}, true);
const callData = getSwapData(swap);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(callData, _protocolFee, 0);
const tx = zeroEx
.executeMetaTransaction(mtx, mtx.signature)
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
return expect(tx).to.revertWith(new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError());
});
});

View File

@ -558,7 +558,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.makerAssetData,
new StringRevertError(RevertReason.TransferFailed).toString(),
new StringRevertError(RevertReason.TransferFailed).encode(),
);
const tx = exchange
.fillOrder(signedOrder, takerAssetFillAmount, signedOrder.signature)
@ -587,7 +587,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.takerAssetData,
new StringRevertError(RevertReason.TransferFailed).toString(),
new StringRevertError(RevertReason.TransferFailed).encode(),
);
const tx = exchange
.fillOrder(signedOrder, takerAssetFillAmount, signedOrder.signature)
@ -616,7 +616,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.makerAssetData,
new StringRevertError(RevertReason.InvalidAmount).toString(),
new StringRevertError(RevertReason.InvalidAmount).encode(),
);
const tx = exchange
.fillOrder(signedOrder, takerAssetFillAmount, signedOrder.signature)
@ -645,7 +645,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
signedOrder.takerAssetData,
new StringRevertError(RevertReason.InvalidAmount).toString(),
new StringRevertError(RevertReason.InvalidAmount).encode(),
);
const tx = exchange
.fillOrder(signedOrder, takerAssetFillAmount, signedOrder.signature)
@ -980,7 +980,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
assetData,
new StringRevertError(RevertReason.TargetNotEven).toString(),
new StringRevertError(RevertReason.TargetNotEven).encode(),
);
const tx = exchange
.fillOrder(signedOrder, signedOrder.takerAssetAmount, signedOrder.signature)
@ -1015,7 +1015,7 @@ blockchainTests.resets('Exchange core', () => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashHex,
assetData,
new StringRevertError(RevertReason.TargetNotEven).toString(),
new StringRevertError(RevertReason.TargetNotEven).encode(),
);
const tx = exchange
.fillOrder(signedOrder, signedOrder.takerAssetAmount, signedOrder.signature)

View File

@ -278,7 +278,7 @@ blockchainTests.resets('Exchange fills dydx orders', env => {
} catch (e) {
assetProxyError = decodeThrownErrorAsRevertError(e).values.errorData;
}
expect(assetProxyError).to.deep.equal(new StringRevertError(expectedAssetProxyError.toString()));
expect(assetProxyError).to.deep.equal(new StringRevertError(expectedAssetProxyError.encode()));
});
});
});

View File

@ -121,7 +121,7 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => {
maker.address,
wethless.address,
'0x',
).toString();
).encode();
return new ExchangeRevertErrors.TransactionExecutionError(
transactionHashUtils.getTransactionHashHex(failedTransaction),
nestedError,
@ -252,7 +252,7 @@ blockchainTests.resets('Transaction <> protocol fee integration tests', env => {
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashUtils.getTransactionHashHex(recursiveTransaction),
protocolFeeError(order, transaction).toString(),
protocolFeeError(order, transaction).encode(),
);
return expect(tx).to.revertWith(expectedError);
});

View File

@ -280,7 +280,7 @@ blockchainTests.resets('Transaction integration tests', env => {
const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError(
transactionHashHex,
transaction.signerAddress,
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
recursiveTransactionHashHex,
noReentrancyError,
@ -330,7 +330,7 @@ blockchainTests.resets('Transaction integration tests', env => {
orderHashUtils.getOrderHashHex(order),
order.makerAddress,
order.signature,
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
@ -353,7 +353,7 @@ blockchainTests.resets('Transaction integration tests', env => {
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(order),
takers[0].address,
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
@ -403,7 +403,7 @@ blockchainTests.resets('Transaction integration tests', env => {
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(orders[0]),
takers[0].address,
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
@ -771,7 +771,7 @@ blockchainTests.resets('Transaction integration tests', env => {
const nestedError = new ExchangeRevertErrors.OrderStatusError(
orderHashUtils.getOrderHashHex(order),
OrderStatus.Cancelled,
).toString();
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashUtils.getTransactionHashHex(transaction2),
nestedError,

View File

@ -123,7 +123,7 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashUtils.getOrderHashHex(order),
order.makerAssetData,
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').toString(),
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
);
return expect(tx).to.revertWith(expectedError);
});
@ -133,7 +133,7 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
orderHashUtils.getOrderHashHex(order),
order.makerAssetData,
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').toString(),
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
);
return expect(tx).to.revertWith(expectedError);
});

View File

@ -1,4 +1,57 @@
[
{
"version": "0.3.0",
"changes": [
{
"note": "Internal audit fixes",
"pr": 2657
},
{
"note": "Add refund mechanism to meta-transactions",
"pr": 2657
},
{
"note": "Pass sender address to transformers",
"pr": 2657
},
{
"note": "Refund unused protocol fees to `refundReceiver` in FQT",
"pr": 2657
},
{
"note": "Fix `TransformerDeployer.kill()` calling the wrong `die()` interface.",
"pr": 2624
},
{
"note": "Address CD post-audit feedback",
"pr": 2657
},
{
"note": "Add `LogMetadataTransformer`",
"pr": 2657
},
{
"note": "Rename all feature contracts to have `Feature` suffix",
"pr": 2657
},
{
"note": "Return `IZeroExContract` in `fullMigrateAsync()`",
"pr": 2657
},
{
"note": "Add taker address enforcement to RFQT orders in FQT",
"pr": 2692
},
{
"note": "All calldata is valid if quote signer is unset in `TransformERC20`",
"pr": 2692
},
{
"note": "Add updated Kyber and Mooniswap rollup to FQT",
"pr": 2692
}
]
},
{
"version": "0.2.0",
"changes": [

View File

@ -19,22 +19,22 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "./features/IOwnable.sol";
import "./features/ISimpleFunctionRegistry.sol";
import "./features/ITokenSpender.sol";
import "./features/ISignatureValidator.sol";
import "./features/ITransformERC20.sol";
import "./features/IMetaTransactions.sol";
import "./features/IOwnableFeature.sol";
import "./features/ISimpleFunctionRegistryFeature.sol";
import "./features/ITokenSpenderFeature.sol";
import "./features/ISignatureValidatorFeature.sol";
import "./features/ITransformERC20Feature.sol";
import "./features/IMetaTransactionsFeature.sol";
/// @dev Interface for a fully featured Exchange Proxy.
interface IZeroEx is
IOwnable,
ISimpleFunctionRegistry,
ITokenSpender,
ISignatureValidator,
ITransformERC20,
IMetaTransactions
IOwnableFeature,
ISimpleFunctionRegistryFeature,
ITokenSpenderFeature,
ISignatureValidatorFeature,
ITransformERC20Feature,
IMetaTransactionsFeature
{
// solhint-disable state-visibility

View File

@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "./migrations/LibBootstrap.sol";
import "./features/Bootstrap.sol";
import "./features/BootstrapFeature.sol";
import "./storage/LibProxyStorage.sol";
import "./errors/LibProxyRichErrors.sol";
@ -32,14 +32,14 @@ contract ZeroEx {
// solhint-disable separate-by-one-line-in-contract,indent,var-name-mixedcase
using LibBytesV06 for bytes;
/// @dev Construct this contract and register the `Bootstrap` feature.
/// @dev Construct this contract and register the `BootstrapFeature` feature.
/// After constructing this contract, `bootstrap()` should be called
/// by `bootstrap()` to seed the initial feature set.
/// @param bootstrapper Who can call `bootstrap()`.
constructor(address bootstrapper) public {
// Temporarily create and register the bootstrap feature.
// It will deregister itself after `bootstrap()` has been called.
Bootstrap bootstrap = new Bootstrap(bootstrapper);
BootstrapFeature bootstrap = new BootstrapFeature(bootstrapper);
LibProxyStorage.getStorage().impls[bootstrap.bootstrap.selector] =
address(bootstrap);
}

View File

@ -34,13 +34,15 @@ library LibCommonRichErrors {
);
}
function IllegalReentrancyError()
function IllegalReentrancyError(bytes4 selector, uint256 reentrancyFlags)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("IllegalReentrancyError()"))
bytes4(keccak256("IllegalReentrancyError(bytes4,uint256)")),
selector,
reentrancyFlags
);
}
}

View File

@ -24,7 +24,7 @@ import "@0x/contracts-utils/contracts/src/v06/AuthorizableV06.sol";
/// @dev A contract with a `die()` function.
interface IKillable {
function die() external;
function die(address payable ethRecipient) external;
}
/// @dev Deployer contract for ERC20 transformers.
@ -48,9 +48,9 @@ contract TransformerDeployer is
mapping (address => uint256) public toDeploymentNonce;
/// @dev Create this contract and register authorities.
constructor(address[] memory authorities) public {
for (uint256 i = 0; i < authorities.length; ++i) {
_addAuthorizedAddress(authorities[i]);
constructor(address[] memory initialAuthorities) public {
for (uint256 i = 0; i < initialAuthorities.length; ++i) {
_addAuthorizedAddress(initialAuthorities[i]);
}
}
@ -67,16 +67,19 @@ contract TransformerDeployer is
assembly {
deployedAddress := create(callvalue(), add(bytecode, 32), mload(bytecode))
}
require(deployedAddress != address(0), 'TransformerDeployer/DEPLOY_FAILED');
toDeploymentNonce[deployedAddress] = deploymentNonce;
emit Deployed(deployedAddress, deploymentNonce, msg.sender);
}
/// @dev Call `die()` on a contract. Only callable by an authority.
function kill(IKillable target)
/// @param target The target contract to call `die()` on.
/// @param ethRecipient The Recipient of any ETH locked in `target`.
function kill(IKillable target, address payable ethRecipient)
public
onlyAuthorized
{
target.die();
target.die(ethRecipient);
emit Killed(address(target), msg.sender);
}
}

View File

@ -22,12 +22,12 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../migrations/LibBootstrap.sol";
import "../storage/LibProxyStorage.sol";
import "./IBootstrap.sol";
import "./IBootstrapFeature.sol";
/// @dev Detachable `bootstrap()` feature.
contract Bootstrap is
IBootstrap
contract BootstrapFeature is
IBootstrapFeature
{
// solhint-disable state-visibility,indent
/// @dev The ZeroEx contract.
@ -69,7 +69,7 @@ contract Bootstrap is
// Deregister.
LibProxyStorage.getStorage().impls[this.bootstrap.selector] = address(0);
// Self-destruct.
Bootstrap(_implementation).die();
BootstrapFeature(_implementation).die();
// Call the bootstrapper.
LibBootstrap.delegatecallBootstrapFunction(target, callData);
}
@ -77,6 +77,7 @@ contract Bootstrap is
/// @dev Self-destructs this contract.
/// Can only be called by the deployer.
function die() external {
assert(address(this) == _implementation);
if (msg.sender != _deployer) {
LibProxyRichErrors.InvalidDieCallerError(msg.sender, _deployer).rrevert();
}

View File

@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
/// @dev Detachable `bootstrap()` feature.
interface IBootstrap {
interface IBootstrapFeature {
/// @dev Bootstrap the initial feature set of this contract by delegatecalling
/// into `target`. Before exiting the `bootstrap()` function will

View File

@ -23,7 +23,7 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
/// @dev Meta-transactions feature.
interface IMetaTransactions {
interface IMetaTransactionsFeature {
/// @dev Describes an exchange proxy meta transaction.
struct MetaTransactionData {

View File

@ -24,7 +24,7 @@ import "@0x/contracts-utils/contracts/src/v06/interfaces/IOwnableV06.sol";
// solhint-disable no-empty-blocks
/// @dev Owner management and migration features.
interface IOwnable is
interface IOwnableFeature is
IOwnableV06
{
/// @dev Emitted when `migrate()` is called.

View File

@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
/// @dev Feature for validating signatures.
interface ISignatureValidator {
interface ISignatureValidatorFeature {
/// @dev Allowed signature types.
enum SignatureType {

View File

@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
/// @dev Basic registry management features.
interface ISimpleFunctionRegistry {
interface ISimpleFunctionRegistryFeature {
/// @dev A function implementation was updated via `extend()` or `rollback()`.
/// @param selector The function selector.

View File

@ -23,7 +23,7 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
/// @dev Feature that allows spending token allowances.
interface ITokenSpender {
interface ITokenSpenderFeature {
/// @dev Transfers ERC20 tokens from `owner` to `to`.
/// Only callable from within.

View File

@ -25,7 +25,7 @@ import "../external/IFlashWallet.sol";
/// @dev Feature to composably transform between ERC20 tokens.
interface ITransformERC20 {
interface ITransformERC20Feature {
/// @dev Defines a transformation to run in `transformERC20()`.
struct Transformation {

View File

@ -21,24 +21,27 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "../errors/LibMetaTransactionsRichErrors.sol";
import "../fixins/FixinCommon.sol";
import "../fixins/FixinReentrancyGuard.sol";
import "../fixins/FixinEIP712.sol";
import "../migrations/LibMigrate.sol";
import "../storage/LibMetaTransactionsStorage.sol";
import "./libs/LibSignedCallData.sol";
import "./IMetaTransactions.sol";
import "./ITransformERC20.sol";
import "./ISignatureValidator.sol";
import "./ITokenSpender.sol";
import "./IMetaTransactionsFeature.sol";
import "./ITransformERC20Feature.sol";
import "./ISignatureValidatorFeature.sol";
import "./ITokenSpenderFeature.sol";
import "./IFeature.sol";
/// @dev MetaTransactions feature.
contract MetaTransactions is
contract MetaTransactionsFeature is
IFeature,
IMetaTransactions,
IMetaTransactionsFeature,
FixinCommon,
FixinReentrancyGuard,
FixinEIP712
{
using LibBytesV06 for bytes;
@ -69,7 +72,7 @@ contract MetaTransactions is
IERC20TokenV06 outputToken;
uint256 inputTokenAmount;
uint256 minOutputTokenAmount;
ITransformERC20.Transformation[] transformations;
ITransformERC20Feature.Transformation[] transformations;
}
/// @dev Name of this feature.
@ -92,6 +95,16 @@ contract MetaTransactions is
")"
);
/// @dev Refunds up to `msg.value` leftover ETH at the end of the call.
modifier refundsAttachedEth() {
_;
uint256 remainingBalance =
LibSafeMathV06.min256(msg.value, address(this).balance);
if (remainingBalance > 0) {
msg.sender.transfer(remainingBalance);
}
}
constructor(address zeroExAddress)
public
FixinCommon()
@ -127,9 +140,11 @@ contract MetaTransactions is
public
payable
override
nonReentrant(REENTRANCY_MTX)
refundsAttachedEth
returns (bytes memory returnResult)
{
return _executeMetaTransactionPrivate(
returnResult = _executeMetaTransactionPrivate(
msg.sender,
mtx,
signature
@ -147,6 +162,8 @@ contract MetaTransactions is
public
payable
override
nonReentrant(REENTRANCY_MTX)
refundsAttachedEth
returns (bytes[] memory returnResults)
{
if (mtxs.length != signatures.length) {
@ -254,29 +271,31 @@ contract MetaTransactions is
_validateMetaTransaction(state);
// Mark the transaction executed.
assert(block.number > 0);
// Mark the transaction executed by storing the block at which it was executed.
// Currently the block number just indicates that the mtx was executed and
// serves no other purpose from within this contract.
LibMetaTransactionsStorage.getStorage()
.mtxHashToExecutedBlockNumber[state.hash] = block.number;
// Execute the call based on the selector.
state.selector = mtx.callData.readBytes4(0);
if (state.selector == ITransformERC20.transformERC20.selector) {
returnResult = _executeTransformERC20Call(state);
} else {
LibMetaTransactionsRichErrors
.MetaTransactionUnsupportedFunctionError(state.hash, state.selector)
.rrevert();
}
// Pay the fee to the sender.
if (mtx.feeAmount > 0) {
ITokenSpender(address(this))._spendERC20Tokens(
ITokenSpenderFeature(address(this))._spendERC20Tokens(
mtx.feeToken,
mtx.signer, // From the signer.
sender, // To the sender.
mtx.feeAmount
);
}
// Execute the call based on the selector.
state.selector = mtx.callData.readBytes4(0);
if (state.selector == ITransformERC20Feature.transformERC20.selector) {
returnResult = _executeTransformERC20Call(state);
} else {
LibMetaTransactionsRichErrors
.MetaTransactionUnsupportedFunctionError(state.hash, state.selector)
.rrevert();
}
emit MetaTransactionExecuted(
state.hash,
state.selector,
@ -330,7 +349,7 @@ contract MetaTransactions is
}
// Must be signed by signer.
try
ISignatureValidator(address(this))
ISignatureValidatorFeature(address(this))
.validateHashSignature(state.hash, state.mtx.signer, state.signature)
{}
catch (bytes memory err) {
@ -353,9 +372,9 @@ contract MetaTransactions is
}
}
/// @dev Execute a `ITransformERC20.transformERC20()` meta-transaction call
/// @dev Execute a `ITransformERC20Feature.transformERC20()` meta-transaction call
/// by decoding the call args and translating the call to the internal
/// `ITransformERC20._transformERC20()` variant, where we can override
/// `ITransformERC20Feature._transformERC20()` variant, where we can override
/// the taker address.
function _executeTransformERC20Call(ExecuteState memory state)
private
@ -367,7 +386,7 @@ contract MetaTransactions is
// since decoding a single struct arg consumes far less stack space than
// decoding multiple struct args.
// Where the encoding for multiple args (with the seleector ommitted)
// Where the encoding for multiple args (with the selector ommitted)
// would typically look like:
// | argument | offset |
// |--------------------------|---------|
@ -394,7 +413,7 @@ contract MetaTransactions is
bytes memory encodedStructArgs = new bytes(state.mtx.callData.length - 4 + 32);
// Copy the args data from the original, after the new struct offset prefix.
bytes memory fromCallData = state.mtx.callData;
assert(fromCallData.length >= 4);
assert(fromCallData.length >= 160);
uint256 fromMem;
uint256 toMem;
assembly {
@ -407,19 +426,19 @@ contract MetaTransactions is
toMem := add(encodedStructArgs, 64)
}
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
// Decode call args for `ITransformERC20.transformERC20()` as a struct.
// Decode call args for `ITransformERC20Feature.transformERC20()` as a struct.
args = abi.decode(encodedStructArgs, (ExternalTransformERC20Args));
}
// Parse the signature and hash out of the calldata so `_transformERC20()`
// can authenticate it.
(bytes32 callDataHash, bytes memory callDataSignature) =
LibSignedCallData.parseCallData(state.mtx.callData);
// Call `ITransformERC20._transformERC20()` (internal variant).
// Call `ITransformERC20Feature._transformERC20()` (internal variant).
return _callSelf(
state.hash,
abi.encodeWithSelector(
ITransformERC20._transformERC20.selector,
ITransformERC20.TransformERC20Args({
ITransformERC20Feature._transformERC20.selector,
ITransformERC20Feature.TransformERC20Args({
taker: state.mtx.signer, // taker is mtx signer
inputToken: args.inputToken,
outputToken: args.outputToken,

View File

@ -26,14 +26,14 @@ import "../storage/LibOwnableStorage.sol";
import "../migrations/LibBootstrap.sol";
import "../migrations/LibMigrate.sol";
import "./IFeature.sol";
import "./IOwnable.sol";
import "./SimpleFunctionRegistry.sol";
import "./IOwnableFeature.sol";
import "./SimpleFunctionRegistryFeature.sol";
/// @dev Owner management features.
contract Ownable is
contract OwnableFeature is
IFeature,
IOwnable,
IOwnableFeature,
FixinCommon
{
@ -44,10 +44,6 @@ contract Ownable is
using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initializes this feature. The intial owner will be set to this (ZeroEx)
/// to allow the bootstrappers to call `extend()`. Ownership should be
/// transferred to the real owner by the bootstrapper after
@ -58,9 +54,9 @@ contract Ownable is
LibOwnableStorage.getStorage().owner = address(this);
// Register feature functions.
SimpleFunctionRegistry(address(this))._extendSelf(this.transferOwnership.selector, _implementation);
SimpleFunctionRegistry(address(this))._extendSelf(this.owner.selector, _implementation);
SimpleFunctionRegistry(address(this))._extendSelf(this.migrate.selector, _implementation);
SimpleFunctionRegistryFeature(address(this))._extendSelf(this.transferOwnership.selector, _implementation);
SimpleFunctionRegistryFeature(address(this))._extendSelf(this.owner.selector, _implementation);
SimpleFunctionRegistryFeature(address(this))._extendSelf(this.migrate.selector, _implementation);
return LibBootstrap.BOOTSTRAP_SUCCESS;
}

View File

@ -24,28 +24,31 @@ import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "../errors/LibSignatureRichErrors.sol";
import "../fixins/FixinCommon.sol";
import "../migrations/LibMigrate.sol";
import "./ISignatureValidator.sol";
import "./ISignatureValidatorFeature.sol";
import "./IFeature.sol";
/// @dev Feature for validating signatures.
contract SignatureValidator is
contract SignatureValidatorFeature is
IFeature,
ISignatureValidator,
ISignatureValidatorFeature,
FixinCommon
{
using LibBytesV06 for bytes;
using LibRichErrorsV06 for bytes;
/// @dev Exclusive upper limit on ECDSA signatures 'R' values.
/// The valid range is given by fig (282) of the yellow paper.
uint256 private constant ECDSA_SIGNATURE_R_LIMIT =
uint256(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141);
/// @dev Exclusive upper limit on ECDSA signatures 'S' values.
/// The valid range is given by fig (283) of the yellow paper.
uint256 private constant ECDSA_SIGNATURE_S_LIMIT = ECDSA_SIGNATURE_R_LIMIT / 2 + 1;
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "SignatureValidator";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`.
/// @return success `LibMigrate.SUCCESS` on success.
@ -160,12 +163,14 @@ contract SignatureValidator is
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
recovered = ecrecover(
hash,
v,
r,
s
);
if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) {
recovered = ecrecover(
hash,
v,
r,
s
);
}
} else if (signatureType == SignatureType.EthSign) {
// Signed using `eth_sign`
if (signature.length != 66) {
@ -179,15 +184,17 @@ contract SignatureValidator is
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
recovered = ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)),
v,
r,
s
);
if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) {
recovered = ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)),
v,
r,
s
);
}
} else {
// This should never happen.
revert('SignatureValidator/ILLEGAL_CODE_PATH');

View File

@ -26,13 +26,13 @@ import "../storage/LibSimpleFunctionRegistryStorage.sol";
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
import "../migrations/LibBootstrap.sol";
import "./IFeature.sol";
import "./ISimpleFunctionRegistry.sol";
import "./ISimpleFunctionRegistryFeature.sol";
/// @dev Basic registry management features.
contract SimpleFunctionRegistry is
contract SimpleFunctionRegistryFeature is
IFeature,
ISimpleFunctionRegistry,
ISimpleFunctionRegistryFeature,
FixinCommon
{
/// @dev Name of this feature.
@ -42,10 +42,6 @@ contract SimpleFunctionRegistry is
using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initializes this feature, registering its own functions.
/// @return success Magic bytes if successful.
function bootstrap()

View File

@ -28,15 +28,14 @@ import "../fixins/FixinCommon.sol";
import "../migrations/LibMigrate.sol";
import "../external/IAllowanceTarget.sol";
import "../storage/LibTokenSpenderStorage.sol";
import "./ITokenSpender.sol";
import "./ITokenSpenderFeature.sol";
import "./IFeature.sol";
import "./ISimpleFunctionRegistry.sol";
/// @dev Feature that allows spending token allowances.
contract TokenSpender is
contract TokenSpenderFeature is
IFeature,
ITokenSpender,
ITokenSpenderFeature,
FixinCommon
{
// solhint-disable
@ -48,10 +47,6 @@ contract TokenSpender is
using LibRichErrorsV06 for bytes;
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initialize and register this feature. Should be delegatecalled
/// into during a `Migrate.migrate()`.
/// @param allowanceTarget An `allowanceTarget` instance, configured to have

View File

@ -32,17 +32,16 @@ import "../storage/LibTransformERC20Storage.sol";
import "../transformers/IERC20Transformer.sol";
import "../transformers/LibERC20Transformer.sol";
import "./libs/LibSignedCallData.sol";
import "./ITransformERC20.sol";
import "./ITokenSpender.sol";
import "./ITransformERC20Feature.sol";
import "./ITokenSpenderFeature.sol";
import "./IFeature.sol";
import "./ISignatureValidator.sol";
import "./ISimpleFunctionRegistry.sol";
import "./ISignatureValidatorFeature.sol";
/// @dev Feature to composably transform between ERC20 tokens.
contract TransformERC20 is
contract TransformERC20Feature is
IFeature,
ITransformERC20,
ITransformERC20Feature,
FixinCommon
{
using LibSafeMathV06 for uint256;
@ -61,10 +60,6 @@ contract TransformERC20 is
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
constructor() public FixinCommon() {
// solhint-disable-next-line no-empty-blocks
}
/// @dev Initialize and register this feature.
/// Should be delegatecalled by `Migrate.migrate()`.
/// @param transformerDeployer The trusted deployer for transformers.
@ -81,7 +76,10 @@ contract TransformERC20 is
_registerFeatureFunction(this.getQuoteSigner.selector);
_registerFeatureFunction(this.transformERC20.selector);
_registerFeatureFunction(this._transformERC20.selector);
this.createTransformWallet();
if (this.getTransformWallet() == IFlashWallet(address(0))) {
// Create the transform wallet if it doesn't exist.
this.createTransformWallet();
}
LibTransformERC20Storage.getStorage().transformerDeployer = transformerDeployer;
return LibMigrate.MIGRATE_SUCCESS;
}
@ -213,7 +211,7 @@ contract TransformERC20 is
// If the input token amount is -1, transform the taker's entire
// spendable balance.
if (args.inputTokenAmount == uint256(-1)) {
args.inputTokenAmount = ITokenSpender(address(this))
args.inputTokenAmount = ITokenSpenderFeature(address(this))
.getSpendableERC20BalanceOf(args.inputToken, args.taker);
}
@ -257,16 +255,15 @@ contract TransformERC20 is
// Compute how much output token has been transferred to the taker.
state.takerOutputTokenBalanceAfter =
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) {
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
} else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
address(args.outputToken),
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
).rrevert();
}
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
state.takerOutputTokenBalanceBefore
);
// Ensure enough output token has been sent to the taker.
if (outputTokenAmount < args.minOutputTokenAmount) {
LibTransformERC20RichErrors.IncompleteTransformERC20Error(
@ -318,7 +315,7 @@ contract TransformERC20 is
// Transfer input tokens.
if (!LibERC20Transformer.isTokenETH(inputToken)) {
// Token is not ETH, so pull ERC20 tokens.
ITokenSpender(address(this))._spendERC20Tokens(
ITokenSpenderFeature(address(this))._spendERC20Tokens(
inputToken,
from,
to,
@ -360,9 +357,12 @@ contract TransformERC20 is
// Call data.
abi.encodeWithSelector(
IERC20Transformer.transform.selector,
callDataHash,
taker,
transformation.data
IERC20Transformer.TransformContext({
callDataHash: callDataHash,
sender: msg.sender,
taker: taker,
data: transformation.data
})
)
);
// Ensure the transformer returned the magic bytes.
@ -389,13 +389,19 @@ contract TransformERC20 is
view
returns (bytes32 validCallDataHash)
{
address quoteSigner = getQuoteSigner();
if (quoteSigner == address(0)) {
// If no quote signer is configured, then all calldata hashes are
// valid.
return callDataHash;
}
if (signature.length == 0) {
return bytes32(0);
}
if (ISignatureValidator(address(this)).isValidHashSignature(
if (ISignatureValidatorFeature(address(this)).isValidHashSignature(
callDataHash,
getQuoteSigner(),
quoteSigner,
signature
)) {
return callDataHash;

View File

@ -22,8 +22,8 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibCommonRichErrors.sol";
import "../errors/LibOwnableRichErrors.sol";
import "../features/IOwnable.sol";
import "../features/ISimpleFunctionRegistry.sol";
import "../features/IOwnableFeature.sol";
import "../features/ISimpleFunctionRegistryFeature.sol";
/// @dev Common feature utilities.
@ -45,7 +45,7 @@ abstract contract FixinCommon {
/// @dev The caller of this function must be the owner.
modifier onlyOwner() virtual {
{
address owner = IOwnable(address(this)).owner();
address owner = IOwnableFeature(address(this)).owner();
if (msg.sender != owner) {
LibOwnableRichErrors.OnlyOwnerError(
msg.sender,
@ -68,7 +68,7 @@ abstract contract FixinCommon {
function _registerFeatureFunction(bytes4 selector)
internal
{
ISimpleFunctionRegistry(address(this)).extend(selector, _implementation);
ISimpleFunctionRegistryFeature(address(this)).extend(selector, _implementation);
}
/// @dev Encode a feature version as a `uint256`.

View File

@ -22,7 +22,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibCommonRichErrors.sol";
import "../errors/LibOwnableRichErrors.sol";
import "../features/IOwnable.sol";
/// @dev EIP712 helpers for features.

View File

@ -0,0 +1,60 @@
/*
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 "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "../errors/LibCommonRichErrors.sol";
import "../storage/LibReentrancyGuardStorage.sol";
/// @dev Common feature utilities.
abstract contract FixinReentrancyGuard {
using LibRichErrorsV06 for bytes;
using LibBytesV06 for bytes;
// Combinable reentrancy flags.
/// @dev Reentrancy guard flag for meta-transaction functions.
uint256 constant internal REENTRANCY_MTX = 0x1;
/// @dev Cannot reenter a function with the same reentrancy guard flags.
modifier nonReentrant(uint256 reentrancyFlags) virtual {
LibReentrancyGuardStorage.Storage storage stor =
LibReentrancyGuardStorage.getStorage();
{
uint256 currentFlags = stor.reentrancyFlags;
// Revert if any bits in `reentrancyFlags` has already been set.
if ((currentFlags & reentrancyFlags) != 0) {
LibCommonRichErrors.IllegalReentrancyError(
msg.data.readBytes4(0),
reentrancyFlags
).rrevert();
}
// Update reentrancy flags.
stor.reentrancyFlags = currentFlags | reentrancyFlags;
}
_;
// Clear reentrancy flags.
stor.reentrancyFlags = stor.reentrancyFlags & (~reentrancyFlags);
}
}

View File

@ -20,11 +20,11 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../ZeroEx.sol";
import "../features/IOwnable.sol";
import "../features/TokenSpender.sol";
import "../features/TransformERC20.sol";
import "../features/SignatureValidator.sol";
import "../features/MetaTransactions.sol";
import "../features/IOwnableFeature.sol";
import "../features/TokenSpenderFeature.sol";
import "../features/TransformERC20Feature.sol";
import "../features/SignatureValidatorFeature.sol";
import "../features/MetaTransactionsFeature.sol";
import "../external/AllowanceTarget.sol";
import "./InitialMigration.sol";
@ -36,12 +36,12 @@ contract FullMigration {
/// @dev Features to add the the proxy contract.
struct Features {
SimpleFunctionRegistry registry;
Ownable ownable;
TokenSpender tokenSpender;
TransformERC20 transformERC20;
SignatureValidator signatureValidator;
MetaTransactions metaTransactions;
SimpleFunctionRegistryFeature registry;
OwnableFeature ownable;
TokenSpenderFeature tokenSpender;
TransformERC20Feature transformERC20;
SignatureValidatorFeature signatureValidator;
MetaTransactionsFeature metaTransactions;
}
/// @dev Parameters needed to initialize features.
@ -109,7 +109,7 @@ contract FullMigration {
_addFeatures(zeroEx, owner, features, migrateOpts);
// Transfer ownership to the real owner.
IOwnable(address(zeroEx)).transferOwnership(owner);
IOwnableFeature(address(zeroEx)).transferOwnership(owner);
// Self-destruct.
this.die(owner);
@ -142,8 +142,8 @@ contract FullMigration {
)
private
{
IOwnable ownable = IOwnable(address(zeroEx));
// TokenSpender
IOwnableFeature ownable = IOwnableFeature(address(zeroEx));
// TokenSpenderFeature
{
// Create the allowance target.
AllowanceTarget allowanceTarget = new AllowanceTarget();
@ -155,42 +155,42 @@ contract FullMigration {
ownable.migrate(
address(features.tokenSpender),
abi.encodeWithSelector(
TokenSpender.migrate.selector,
TokenSpenderFeature.migrate.selector,
allowanceTarget
),
address(this)
);
}
// TransformERC20
// TransformERC20Feature
{
// Register the feature.
ownable.migrate(
address(features.transformERC20),
abi.encodeWithSelector(
TransformERC20.migrate.selector,
TransformERC20Feature.migrate.selector,
migrateOpts.transformerDeployer
),
address(this)
);
}
// SignatureValidator
// SignatureValidatorFeature
{
// Register the feature.
ownable.migrate(
address(features.signatureValidator),
abi.encodeWithSelector(
SignatureValidator.migrate.selector
SignatureValidatorFeature.migrate.selector
),
address(this)
);
}
// MetaTransactions
// MetaTransactionsFeature
{
// Register the feature.
ownable.migrate(
address(features.metaTransactions),
abi.encodeWithSelector(
MetaTransactions.migrate.selector
MetaTransactionsFeature.migrate.selector
),
address(this)
);

View File

@ -20,9 +20,9 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../ZeroEx.sol";
import "../features/IBootstrap.sol";
import "../features/SimpleFunctionRegistry.sol";
import "../features/Ownable.sol";
import "../features/IBootstrapFeature.sol";
import "../features/SimpleFunctionRegistryFeature.sol";
import "../features/OwnableFeature.sol";
import "./LibBootstrap.sol";
@ -31,8 +31,8 @@ contract InitialMigration {
/// @dev Features to bootstrap into the the proxy contract.
struct BootstrapFeatures {
SimpleFunctionRegistry registry;
Ownable ownable;
SimpleFunctionRegistryFeature registry;
OwnableFeature ownable;
}
/// @dev The allowed caller of `initializeZeroEx()`. In production, this would be
@ -70,7 +70,7 @@ contract InitialMigration {
require(msg.sender == initializeCaller, "InitialMigration/INVALID_SENDER");
// Bootstrap the initial feature set.
IBootstrap(address(zeroEx)).bootstrap(
IBootstrapFeature(address(zeroEx)).bootstrap(
address(this),
abi.encodeWithSelector(this.bootstrap.selector, owner, features)
);
@ -99,26 +99,26 @@ contract InitialMigration {
LibBootstrap.delegatecallBootstrapFunction(
address(features.registry),
abi.encodeWithSelector(
SimpleFunctionRegistry.bootstrap.selector
SimpleFunctionRegistryFeature.bootstrap.selector
)
);
// Initialize Ownable.
// Initialize OwnableFeature.
LibBootstrap.delegatecallBootstrapFunction(
address(features.ownable),
abi.encodeWithSelector(
Ownable.bootstrap.selector
OwnableFeature.bootstrap.selector
)
);
// De-register `SimpleFunctionRegistry._extendSelf`.
SimpleFunctionRegistry(address(this)).rollback(
SimpleFunctionRegistry._extendSelf.selector,
// De-register `SimpleFunctionRegistryFeature._extendSelf`.
SimpleFunctionRegistryFeature(address(this)).rollback(
SimpleFunctionRegistryFeature._extendSelf.selector,
address(0)
);
// Transfer ownership to the real owner.
Ownable(address(this)).transferOwnership(owner);
OwnableFeature(address(this)).transferOwnership(owner);
success = LibBootstrap.BOOTSTRAP_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 "./LibStorage.sol";
import "../external/IFlashWallet.sol";
/// @dev Storage helpers for the `FixinReentrancyGuard` mixin.
library LibReentrancyGuardStorage {
/// @dev Storage bucket for this feature.
struct Storage {
// Reentrancy flags set whenever a non-reentrant function is entered
// and cleared when it is exited.
uint256 reentrancyFlags;
}
/// @dev Get the storage bucket for this contract.
function getStorage() internal pure returns (Storage storage stor) {
uint256 storageSlot = LibStorage.getStorageSlot(
LibStorage.StorageId.ReentrancyGuard
);
// Dip into assembly to change the slot pointed to by the local
// variable `stor`.
// See https://solidity.readthedocs.io/en/v0.6.8/assembly.html?highlight=slot#access-to-external-variables-functions-and-libraries
assembly { stor_slot := storageSlot }
}
}

View File

@ -35,7 +35,8 @@ library LibStorage {
Ownable,
TokenSpender,
TransformERC20,
MetaTransactions
MetaTransactions,
ReentrancyGuard
}
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced

View File

@ -32,7 +32,6 @@ import "./LibERC20Transformer.sol";
contract AffiliateFeeTransformer is
Transformer
{
// solhint-disable no-empty-blocks
using LibRichErrorsV06 for bytes;
using LibSafeMathV06 for uint256;
using LibERC20Transformer for IERC20TokenV06;
@ -51,25 +50,15 @@ contract AffiliateFeeTransformer is
uint256 private constant MAX_UINT256 = uint256(-1);
/// @dev Create this contract.
constructor()
public
Transformer()
{}
/// @dev Transfers tokens to recipients.
/// @param data ABI-encoded `TokenFee[]`, indicating which tokens to transfer.
/// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
function transform(
bytes32, // callDataHash,
address payable, // taker,
bytes calldata data
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)
{
TokenFee[] memory fees = abi.decode(data, (TokenFee[]));
TokenFee[] memory fees = abi.decode(context.data, (TokenFee[]));
// Transfer tokens to recipients.
for (uint256 i = 0; i < fees.length; ++i) {

View File

@ -27,7 +27,7 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
import "../errors/LibTransformERC20RichErrors.sol";
import "../vendor/v3/IExchange.sol";
import "../bridges/IBridgeAdapter.sol";
import "./bridges/IBridgeAdapter.sol";
import "./Transformer.sol";
import "./LibERC20Transformer.sol";
@ -50,7 +50,7 @@ contract FillQuoteTransformer is
/// @dev Transform data to ABI-encode and pass into `transform()`.
struct TransformData {
// Whether we aer performing a market sell or buy.
// Whether we are performing a market sell or buy.
Side side;
// The token being sold.
// This should be an actual token, not the ETH pseudo-token.
@ -71,6 +71,15 @@ contract FillQuoteTransformer is
// For sells, this may be `uint256(-1)` to sell the entire balance of
// `sellToken`.
uint256 fillAmount;
// Who to transfer unused protocol fees to.
// May be a valid address or one of:
// `address(0)`: Stay in flash wallet.
// `address(1)`: Send to the taker.
// `address(2)`: Send to the sender (caller of `transformERC20()`).
address payable refundReceiver;
// Required taker address for RFQT orders.
// Null means any taker can fill it.
address rfqtTakerAddress;
}
/// @dev Results of a call to `_fillOrder()`.
@ -90,6 +99,7 @@ contract FillQuoteTransformer is
uint256 soldAmount;
uint256 protocolFee;
uint256 takerTokenBalanceRemaining;
bool isRfqtAllowed;
}
/// @dev Emitted when a trade is skipped due to a lack of funds
@ -108,6 +118,12 @@ contract FillQuoteTransformer is
bytes4 private constant ERC20_BRIDGE_PROXY_ID = 0xdc1600f3;
/// @dev Maximum uint256 value.
uint256 private constant MAX_UINT256 = uint256(-1);
/// @dev If `refundReceiver` is set to this address, unpsent
/// protocol fees will be sent to the taker.
address private constant REFUND_RECEIVER_TAKER = address(1);
/// @dev If `refundReceiver` is set to this address, unpsent
/// protocol fees will be sent to the sender.
address private constant REFUND_RECEIVER_SENDER = address(2);
/// @dev The Exchange contract.
IExchange public immutable exchange;
@ -130,31 +146,27 @@ contract FillQuoteTransformer is
/// @dev Sell this contract's entire balance of of `sellToken` in exchange
/// for `buyToken` by filling `orders`. Protocol fees should be attached
/// to this call. `buyToken` and excess ETH will be transferred back to the caller.
/// @param data_ ABI-encoded `TransformData`.
/// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
function transform(
bytes32, // callDataHash,
address payable, // taker,
bytes calldata data_
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)
{
TransformData memory data = abi.decode(data_, (TransformData));
TransformData memory data = abi.decode(context.data, (TransformData));
FillState memory state;
// Validate data fields.
if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
LibTransformERC20RichErrors.InvalidTransformDataError(
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_TOKENS,
data_
context.data
).rrevert();
}
if (data.orders.length != data.signatures.length) {
LibTransformERC20RichErrors.InvalidTransformDataError(
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH,
data_
context.data
).rrevert();
}
@ -170,9 +182,14 @@ contract FillQuoteTransformer is
// Approve the ERC20 proxy to spend `sellToken`.
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
// Fill the orders.
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
state.ethRemaining = address(this).balance;
// RFQT orders can only be filled if we have a valid calldata hash
// (calldata was signed), and the actual taker matches the RFQT taker (if set).
state.isRfqtAllowed = context.callDataHash != bytes32(0)
&& (data.rfqtTakerAddress == address(0) || context.taker == data.rfqtTakerAddress);
// Fill the orders.
for (uint256 i = 0; i < data.orders.length; ++i) {
// Check if we've hit our targets.
if (data.side == Side.Sell) {
@ -248,6 +265,17 @@ contract FillQuoteTransformer is
).rrevert();
}
}
// Refund unspent protocol fees.
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
if (data.refundReceiver == REFUND_RECEIVER_TAKER) {
context.taker.transfer(state.ethRemaining);
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
context.sender.transfer(state.ethRemaining);
} else {
data.refundReceiver.transfer(state.ethRemaining);
}
}
return LibERC20Transformer.TRANSFORMER_SUCCESS;
}
@ -415,6 +443,11 @@ contract FillQuoteTransformer is
}
return results;
} else {
// If the order taker address is set to this contract's address then
// this is an RFQT order, and we will only fill it if allowed to.
if (order.takerAddress == address(this) && !state.isRfqtAllowed) {
return results; // Empty results.
}
// Emit an event if we do not have sufficient ETH to cover the protocol fee.
if (state.ethRemaining < state.protocolFee) {
emit ProtocolFeeUnfunded(state.ethRemaining, state.protocolFee);

View File

@ -25,17 +25,25 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
/// @dev A transformation callback used in `TransformERC20.transformERC20()`.
interface IERC20Transformer {
/// @dev Context information to pass into `transform()` by `TransformERC20.transformERC20()`.
struct TransformContext {
// The hash of the `TransformERC20.transformERC20()` calldata.
// Will be null if the calldata is not signed.
bytes32 callDataHash;
// The caller of `TransformERC20.transformERC20()`.
address payable sender;
// taker The taker address, which may be distinct from `sender` in the case
// meta-transactions.
address payable taker;
// Arbitrary data to pass to the transformer.
bytes data;
}
/// @dev Called from `TransformERC20.transformERC20()`. This will be
/// delegatecalled in the context of the FlashWallet instance being used.
/// @param callDataHash The hash of the `TransformERC20.transformERC20()` calldata.
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
/// @param data Arbitrary data to pass to the transformer.
/// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
function transform(
bytes32 callDataHash,
address payable taker,
bytes calldata data
)
function transform(TransformContext calldata context)
external
returns (bytes4 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

@ -56,19 +56,14 @@ contract PayTakerTransformer is
{}
/// @dev Forwards tokens to the taker.
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
/// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer.
/// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
function transform(
bytes32, // callDataHash,
address payable taker,
bytes calldata data_
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)
{
TransformData memory data = abi.decode(data_, (TransformData));
TransformData memory data = abi.decode(context.data, (TransformData));
// Transfer tokens directly to the taker.
for (uint256 i = 0; i < data.tokens.length; ++i) {
@ -79,7 +74,7 @@ contract PayTakerTransformer is
amount = data.tokens[i].getTokenBalanceOf(address(this));
}
if (amount != 0) {
data.tokens[i].transformerTransfer(taker, amount);
data.tokens[i].transformerTransfer(context.taker, amount);
}
}
return LibERC20Transformer.TRANSFORMER_SUCCESS;

View File

@ -59,22 +59,18 @@ contract WethTransformer is
}
/// @dev Wraps and unwraps WETH.
/// @param data_ ABI-encoded `TransformData`, indicating which token to wrap/umwrap.
/// @param context Context information.
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
function transform(
bytes32, // callDataHash,
address payable, // taker,
bytes calldata data_
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)
{
TransformData memory data = abi.decode(data_, (TransformData));
TransformData memory data = abi.decode(context.data, (TransformData));
if (!data.token.isTokenETH() && data.token != weth) {
LibTransformERC20RichErrors.InvalidTransformDataError(
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_TOKENS,
data_
context.data
).rrevert();
}

View File

@ -23,6 +23,7 @@ import "./mixins/MixinAdapterAddresses.sol";
import "./mixins/MixinBalancer.sol";
import "./mixins/MixinCurve.sol";
import "./mixins/MixinKyber.sol";
import "./mixins/MixinMooniswap.sol";
import "./mixins/MixinMStable.sol";
import "./mixins/MixinOasis.sol";
import "./mixins/MixinUniswap.sol";
@ -34,6 +35,7 @@ contract BridgeAdapter is
MixinBalancer,
MixinCurve,
MixinKyber,
MixinMooniswap,
MixinMStable,
MixinOasis,
MixinUniswap,
@ -44,6 +46,7 @@ contract BridgeAdapter is
address private immutable BALANCER_BRIDGE_ADDRESS;
address private immutable CURVE_BRIDGE_ADDRESS;
address private immutable KYBER_BRIDGE_ADDRESS;
address private immutable MOONISWAP_BRIDGE_ADDRESS;
address private immutable MSTABLE_BRIDGE_ADDRESS;
address private immutable OASIS_BRIDGE_ADDRESS;
address private immutable UNISWAP_BRIDGE_ADDRESS;
@ -57,8 +60,8 @@ contract BridgeAdapter is
/// @param from The bridge address, indicating the underlying source of the fill.
/// @param to The `to` address, currrently `address(this)`
event ERC20BridgeTransfer(
address inputToken,
address outputToken,
IERC20TokenV06 inputToken,
IERC20TokenV06 outputToken,
uint256 inputTokenAmount,
uint256 outputTokenAmount,
address from,
@ -70,6 +73,7 @@ contract BridgeAdapter is
MixinBalancer()
MixinCurve()
MixinKyber(addresses)
MixinMooniswap(addresses)
MixinMStable(addresses)
MixinOasis(addresses)
MixinUniswap(addresses)
@ -79,6 +83,7 @@ contract BridgeAdapter is
BALANCER_BRIDGE_ADDRESS = addresses.balancerBridge;
CURVE_BRIDGE_ADDRESS = addresses.curveBridge;
KYBER_BRIDGE_ADDRESS = addresses.kyberBridge;
MOONISWAP_BRIDGE_ADDRESS = addresses.mooniswapBridge;
MSTABLE_BRIDGE_ADDRESS = addresses.mStableBridge;
OASIS_BRIDGE_ADDRESS = addresses.oasisBridge;
UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
@ -87,19 +92,19 @@ contract BridgeAdapter is
function trade(
bytes calldata makerAssetData,
address fromTokenAddress,
IERC20TokenV06 sellToken,
uint256 sellAmount
)
external
returns (uint256 boughtAmount)
{
(
address toTokenAddress,
IERC20TokenV06 buyToken,
address bridgeAddress,
bytes memory bridgeData
) = abi.decode(
makerAssetData[4:],
(address, address, bytes)
(IERC20TokenV06, address, bytes)
);
require(
bridgeAddress != address(this) && bridgeAddress != address(0),
@ -108,65 +113,71 @@ contract BridgeAdapter is
if (bridgeAddress == CURVE_BRIDGE_ADDRESS) {
boughtAmount = _tradeCurve(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == UNISWAP_V2_BRIDGE_ADDRESS) {
boughtAmount = _tradeUniswapV2(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == UNISWAP_BRIDGE_ADDRESS) {
boughtAmount = _tradeUniswap(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == BALANCER_BRIDGE_ADDRESS) {
boughtAmount = _tradeBalancer(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == KYBER_BRIDGE_ADDRESS) {
boughtAmount = _tradeKyber(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == MOONISWAP_BRIDGE_ADDRESS) {
boughtAmount = _tradeMooniswap(
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == MSTABLE_BRIDGE_ADDRESS) {
boughtAmount = _tradeMStable(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else if (bridgeAddress == OASIS_BRIDGE_ADDRESS) {
boughtAmount = _tradeOasis(
toTokenAddress,
buyToken,
sellAmount,
bridgeData
);
} else {
boughtAmount = _tradeZeroExBridge(
bridgeAddress,
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
bridgeData
);
// Do not emit an event. The bridge contract should emit one itself.
return boughtAmount;
}
emit ERC20BridgeTransfer(
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
boughtAmount,
bridgeAddress,
address(this)
);
return boughtAmount;
}
}

View File

@ -26,6 +26,7 @@ contract MixinAdapterAddresses
address balancerBridge;
address curveBridge;
address kyberBridge;
address mooniswapBridge;
address mStableBridge;
address oasisBridge;
address uniswapBridge;

View File

@ -32,9 +32,9 @@ interface IBalancerPool {
/// @return spotPriceAfter The new marginal spot price of the given
/// token pair for this pool.
function swapExactAmountIn(
address tokenIn,
IERC20TokenV06 tokenIn,
uint tokenAmountIn,
address tokenOut,
IERC20TokenV06 tokenOut,
uint minAmountOut,
uint maxPrice
) external returns (uint tokenAmountOut, uint spotPriceAfter);
@ -45,7 +45,7 @@ contract MixinBalancer {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeBalancer(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
@ -53,21 +53,21 @@ contract MixinBalancer {
returns (uint256 boughtAmount)
{
// Decode the bridge data.
(address fromTokenAddress, address poolAddress) = abi.decode(
(IERC20TokenV06 sellToken, IBalancerPool pool) = abi.decode(
bridgeData,
(address, address)
(IERC20TokenV06, IBalancerPool)
);
IERC20TokenV06(fromTokenAddress).approveIfBelow(
poolAddress,
sellToken.approveIfBelow(
address(pool),
sellAmount
);
// Sell all of this contract's `fromTokenAddress` token balance.
(boughtAmount,) = IBalancerPool(poolAddress).swapExactAmountIn(
fromTokenAddress, // tokenIn
sellAmount, // tokenAmountIn
toTokenAddress, // tokenOut
1, // minAmountOut
uint256(-1) // maxPrice
// Sell all of this contract's `sellToken` token balance.
(boughtAmount,) = pool.swapExactAmountIn(
sellToken, // tokenIn
sellAmount, // tokenAmountIn
buyToken, // tokenOut
1, // minAmountOut
uint256(-1) // maxPrice
);
return boughtAmount;
}

View File

@ -34,13 +34,13 @@ contract MixinCurve {
struct CurveBridgeData {
address curveAddress;
bytes4 exchangeFunctionSelector;
address fromTokenAddress;
IERC20TokenV06 sellToken;
int128 fromCoinIdx;
int128 toCoinIdx;
}
function _tradeCurve(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
@ -49,8 +49,8 @@ contract MixinCurve {
{
// Decode the bridge data to get the Curve metadata.
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
IERC20TokenV06(data.fromTokenAddress).approveIfBelow(data.curveAddress, sellAmount);
uint256 beforeBalance = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
data.sellToken.approveIfBelow(data.curveAddress, sellAmount);
uint256 beforeBalance = buyToken.balanceOf(address(this));
(bool success, bytes memory resultData) =
data.curveAddress.call(abi.encodeWithSelector(
data.exchangeFunctionSelector,
@ -64,7 +64,6 @@ contract MixinCurve {
if (!success) {
resultData.rrevert();
}
return IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(beforeBalance);
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
}
}

View File

@ -26,34 +26,36 @@ import "./MixinAdapterAddresses.sol";
interface IKyberNetworkProxy {
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens.
/// @param sellTokenAddress Token to sell.
/// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens
/// using a hint for the reserve.
/// @param sellToken Token to sell.
/// @param sellAmount Amount of tokens to sell.
/// @param buyTokenAddress Token to buy.
/// @param buyToken Token to buy.
/// @param recipientAddress Address to send bought tokens to.
/// @param maxBuyTokenAmount A limit on the amount of tokens to buy.
/// @param minConversionRate The minimal conversion rate. If actual rate
/// is lower, trade is canceled.
/// @param walletId The wallet ID to send part of the fees
/// @param hint The hint for the selective inclusion (or exclusion) of reserves
/// @return boughtAmount Amount of tokens bought.
function trade(
address sellTokenAddress,
function tradeWithHint(
IERC20TokenV06 sellToken,
uint256 sellAmount,
address buyTokenAddress,
IERC20TokenV06 buyToken,
address payable recipientAddress,
uint256 maxBuyTokenAmount,
uint256 minConversionRate,
address walletId
address payable walletId,
bytes calldata hint
)
external
payable
returns(uint256 boughtAmount);
returns (uint256 boughtAmount);
}
contract MixinKyber is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Address indicating the trade is using ETH
@ -71,41 +73,39 @@ contract MixinKyber is
}
function _tradeKyber(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
address fromTokenAddress = abi.decode(bridgeData, (address));
uint256 payableAmount;
(IERC20TokenV06 sellToken, bytes memory hint) =
abi.decode(bridgeData, (IERC20TokenV06, bytes));
if (fromTokenAddress != address(WETH)) {
uint256 payableAmount = 0;
if (sellToken != WETH) {
// If the input token is not WETH, grant an allowance to the exchange
// to spend them.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(KYBER_NETWORK_PROXY),
sellAmount
);
} else {
// If the input token is WETH, unwrap it and attach it to the call.
fromTokenAddress = KYBER_ETH_ADDRESS;
payableAmount = sellAmount;
WETH.withdraw(payableAmount);
}
bool isToTokenWeth = toTokenAddress == address(WETH);
// Try to sell all of this contract's input token balance through
// `KyberNetworkProxy.trade()`.
boughtAmount = KYBER_NETWORK_PROXY.trade{ value: payableAmount }(
boughtAmount = KYBER_NETWORK_PROXY.tradeWithHint{ value: payableAmount }(
// Input token.
fromTokenAddress,
sellToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : sellToken,
// Sell amount.
sellAmount,
// Output token.
isToTokenWeth ? KYBER_ETH_ADDRESS : toTokenAddress,
buyToken == WETH ? IERC20TokenV06(KYBER_ETH_ADDRESS) : buyToken,
// Transfer to this contract
address(uint160(address(this))),
// Buy as much as possible.
@ -113,9 +113,11 @@ contract MixinKyber is
// Lowest minimum conversion rate
1,
// No affiliate address.
address(0)
address(0),
hint
);
if (isToTokenWeth) {
// If receving ETH, wrap it to WETH.
if (buyToken == WETH) {
WETH.deposit{ value: boughtAmount }();
}
return boughtAmount;

View File

@ -27,19 +27,18 @@ import "./MixinAdapterAddresses.sol";
interface IMStable {
function swap(
address _input,
address _output,
uint256 _quantity,
address _recipient
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
address recipient
)
external
returns (uint256 output);
returns (uint256 boughtAmount);
}
contract MixinMStable is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the mStable mUSD contract.
@ -52,21 +51,21 @@ contract MixinMStable is
}
function _tradeMStable(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
IERC20TokenV06(fromTokenAddress).approveIfBelow(address(MSTABLE), sellAmount);
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Grant an allowance to the exchange to spend `sellToken` token.
sellToken.approveIfBelow(address(MSTABLE), sellAmount);
boughtAmount = MSTABLE.swap(
fromTokenAddress,
toTokenAddress,
sellToken,
buyToken,
sellAmount,
address(this)
);

View File

@ -0,0 +1,97 @@
/*
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 "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "./MixinAdapterAddresses.sol";
/// @dev Moooniswap pool interface.
interface IMooniswapPool {
function swap(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
uint256 minBoughtAmount,
address referrer
)
external
payable
returns (uint256 boughtAmount);
}
/// @dev BridgeAdapter mixin for mooniswap.
contract MixinMooniswap is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
using LibERC20TokenV06 for IEtherTokenV06;
/// @dev WETH token.
IEtherTokenV06 private immutable WETH;
constructor(AdapterAddresses memory addresses)
public
{
WETH = IEtherTokenV06(addresses.weth);
}
function _tradeMooniswap(
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(IERC20TokenV06 sellToken, IMooniswapPool pool) =
abi.decode(bridgeData, (IERC20TokenV06, IMooniswapPool));
// Convert WETH to ETH.
uint256 ethValue = 0;
if (sellToken == WETH) {
WETH.withdraw(sellAmount);
ethValue = sellAmount;
} else {
// Grant the pool an allowance.
sellToken.approveIfBelow(
address(pool),
sellAmount
);
}
boughtAmount = pool.swap{value: ethValue}(
sellToken == WETH ? IERC20TokenV06(0) : sellToken,
buyToken == WETH ? IERC20TokenV06(0) : buyToken,
sellAmount,
1,
address(0)
);
// Wrap ETH to WETH.
if (buyToken == WETH) {
WETH.deposit{value:boughtAmount}();
}
}
}

View File

@ -25,26 +25,25 @@ import "./MixinAdapterAddresses.sol";
interface IOasis {
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
/// @param fromToken The token being sold.
/// @param sellAmount The amount of `fromToken` token being sold.
/// @param toToken The token being bought.
/// @param minFillAmount Minimum amount of `toToken` token to buy.
/// @return fillAmount Amount of `toToken` bought.
/// @dev Sell `sellAmount` of `sellToken` token and receive `buyToken` token.
/// @param sellToken The token being sold.
/// @param sellAmount The amount of `sellToken` token being sold.
/// @param buyToken The token being bought.
/// @param minBoughtAmount Minimum amount of `buyToken` token to buy.
/// @return boughtAmount Amount of `buyToken` bought.
function sellAllAmount(
address fromToken,
IERC20TokenV06 sellToken,
uint256 sellAmount,
address toToken,
uint256 minFillAmount
IERC20TokenV06 buyToken,
uint256 minBoughtAmount
)
external
returns (uint256 fillAmount);
returns (uint256 boughtAmount);
}
contract MixinOasis is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the Oasis `MatchingMarket` contract.
@ -57,25 +56,25 @@ contract MixinOasis is
}
function _tradeOasis(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Grant an allowance to the exchange to spend `sellToken` token.
sellToken.approveIfBelow(
address(OASIS),
sellAmount
);
// Try to sell all of this contract's `fromTokenAddress` token balance.
// Try to sell all of this contract's `sellToken` token balance.
boughtAmount = OASIS.sellAllAmount(
fromTokenAddress,
sellToken,
sellAmount,
toTokenAddress,
buyToken,
// min fill amount
1
);

View File

@ -27,11 +27,11 @@ import "./MixinAdapterAddresses.sol";
interface IUniswapExchangeFactory {
/// @dev Get the exchange for a token.
/// @param tokenAddress The address of the token contract.
function getExchange(address tokenAddress)
/// @param token The token contract.
function getExchange(IERC20TokenV06 token)
external
view
returns (address);
returns (IUniswapExchange exchange);
}
interface IUniswapExchange {
@ -71,7 +71,7 @@ interface IUniswapExchange {
/// @param minEthBought The minimum amount of intermediate ETH to buy.
/// @param deadline Time when this order expires.
/// @param recipient Who to transfer the tokens to.
/// @param toTokenAddress The token being bought.
/// @param buyToken The token being bought.
/// @return tokensBought Amount of tokens bought.
function tokenToTokenTransferInput(
uint256 tokensSold,
@ -79,7 +79,7 @@ interface IUniswapExchange {
uint256 minEthBought,
uint256 deadline,
address recipient,
address toTokenAddress
IERC20TokenV06 buyToken
)
external
returns (uint256 tokensBought);
@ -89,14 +89,14 @@ interface IUniswapExchange {
/// @param minTokensBought The minimum number of tokens to buy.
/// @param minEthBought The minimum amount of intermediate ETH to buy.
/// @param deadline Time when this order expires.
/// @param toTokenAddress The token being bought.
/// @param buyToken The token being bought.
/// @return tokensBought Amount of tokens bought.
function tokenToTokenSwapInput(
uint256 tokensSold,
uint256 minTokensBought,
uint256 minEthBought,
uint256 deadline,
address toTokenAddress
IERC20TokenV06 buyToken
)
external
returns (uint256 tokensBought);
@ -105,7 +105,6 @@ interface IUniswapExchange {
contract MixinUniswap is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the WETH contract.
@ -121,27 +120,27 @@ contract MixinUniswap is
}
function _tradeUniswap(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
(address fromTokenAddress) = abi.decode(bridgeData, (address));
// Decode the bridge data to get the `sellToken`.
(IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));
// Get the exchange for the token pair.
IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
fromTokenAddress,
toTokenAddress
sellToken,
buyToken
);
// Convert from WETH to a token.
if (fromTokenAddress == address(WETH)) {
if (sellToken == WETH) {
// Unwrap the WETH.
WETH.withdraw(sellAmount);
// Buy as much of `toTokenAddress` token with ETH as possible
// Buy as much of `buyToken` token with ETH as possible
boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
// Minimum buy amount.
1,
@ -152,13 +151,13 @@ contract MixinUniswap is
);
// Convert from a token to WETH.
} else if (toTokenAddress == address(WETH)) {
} else if (buyToken == WETH) {
// Grant the exchange an allowance.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(exchange),
sellAmount
);
// Buy as much ETH with `fromTokenAddress` token as possible.
// Buy as much ETH with `sellToken` token as possible.
boughtAmount = exchange.tokenToEthSwapInput(
// Sell all tokens we hold.
sellAmount,
@ -172,11 +171,11 @@ contract MixinUniswap is
// Convert from one token to another.
} else {
// Grant the exchange an allowance.
IERC20TokenV06(fromTokenAddress).approveIfBelow(
sellToken.approveIfBelow(
address(exchange),
sellAmount
);
// Buy as much `toTokenAddress` token with `fromTokenAddress` token
// Buy as much `buyToken` token with `sellToken` token
boughtAmount = exchange.tokenToTokenSwapInput(
// Sell all tokens we hold.
sellAmount,
@ -186,8 +185,8 @@ contract MixinUniswap is
1,
// Expires after this block.
block.timestamp,
// Convert to `toTokenAddress`.
toTokenAddress
// Convert to `buyToken`.
buyToken
);
}
@ -197,24 +196,21 @@ contract MixinUniswap is
/// @dev Retrieves the uniswap exchange for a given token pair.
/// In the case of a WETH-token exchange, this will be the non-WETH token.
/// In th ecase of a token-token exchange, this will be the first token.
/// @param fromTokenAddress The address of the token we are converting from.
/// @param toTokenAddress The address of the token we are converting to.
/// @param sellToken The address of the token we are converting from.
/// @param buyToken The address of the token we are converting to.
/// @return exchange The uniswap exchange.
function _getUniswapExchangeForTokenPair(
address fromTokenAddress,
address toTokenAddress
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken
)
private
view
returns (IUniswapExchange exchange)
{
address exchangeTokenAddress = fromTokenAddress;
// Whichever isn't WETH is the exchange token.
if (fromTokenAddress == address(WETH)) {
exchangeTokenAddress = toTokenAddress;
}
exchange = IUniswapExchange(UNISWAP_EXCHANGE_FACTORY.getExchange(exchangeTokenAddress));
exchange = sellToken == WETH
? UNISWAP_EXCHANGE_FACTORY.getExchange(buyToken)
: UNISWAP_EXCHANGE_FACTORY.getExchange(sellToken);
require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
return exchange;
}
}

View File

@ -50,7 +50,6 @@ interface IUniswapV2Router02 {
contract MixinUniswapV2 is
MixinAdapterAddresses
{
using LibERC20TokenV06 for IERC20TokenV06;
/// @dev Mainnet address of the `UniswapV2Router02` contract.
@ -63,21 +62,23 @@ contract MixinUniswapV2 is
}
function _tradeUniswapV2(
address toTokenAddress,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256)
returns (uint256 boughtAmount)
{
// Decode the bridge data to get the `fromTokenAddress`.
// solhint-disable indent
address[] memory path = abi.decode(bridgeData, (address[]));
// solhint-enable indent
require(path.length >= 2, "UniswapV2Bridge/PATH_LENGTH_MUST_BE_AT_LEAST_TWO");
require(path[path.length - 1] == toTokenAddress, "UniswapV2Bridge/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN");
// Grant the Uniswap router an allowance.
require(
path[path.length - 1] == address(buyToken),
"UniswapV2Bridge/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN"
);
// Grant the Uniswap router an allowance to sell the first token.
IERC20TokenV06(path[0]).approveIfBelow(
address(UNISWAP_V2_ROUTER),
sellAmount
@ -88,7 +89,7 @@ contract MixinUniswapV2 is
sellAmount,
// Minimum buy amount.
1,
// Convert `fromTokenAddress` to `toTokenAddress`.
// Convert to `buyToken` along this path.
path,
// Recipient is `this`.
address(this),

View File

@ -24,15 +24,15 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
interface IERC20Bridge {
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
/// @param tokenAddress The address of the ERC20 token to transfer.
/// @dev Transfers `amount` of the ERC20 `buyToken` from `from` to `to`.
/// @param buyToken The address of the ERC20 token to transfer.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
/// @return success The magic bytes `0xdc1600f3` if successful.
function bridgeTransferFrom(
address tokenAddress,
IERC20TokenV06 buyToken,
address from,
address to,
uint256 amount,
@ -49,28 +49,27 @@ contract MixinZeroExBridge {
function _tradeZeroExBridge(
address bridgeAddress,
address fromTokenAddress,
address toTokenAddress,
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
uint256 balanceBefore = IERC20TokenV06(toTokenAddress).balanceOf(address(this));
uint256 balanceBefore = buyToken.balanceOf(address(this));
// Trade the good old fashioned way
IERC20TokenV06(fromTokenAddress).compatTransfer(
sellToken.compatTransfer(
bridgeAddress,
sellAmount
);
IERC20Bridge(bridgeAddress).bridgeTransferFrom(
toTokenAddress,
bridgeAddress,
buyToken,
address(bridgeAddress),
address(this),
1, // amount to transfer back from the bridge
bridgeData
);
boughtAmount = IERC20TokenV06(toTokenAddress).balanceOf(address(this)).safeSub(balanceBefore);
boughtAmount = buyToken.balanceOf(address(this)).safeSub(balanceBefore);
}
}

View File

@ -31,6 +31,8 @@ contract TestFillQuoteTransformerHost is
IERC20Transformer transformer,
TestMintableERC20Token inputToken,
uint256 inputTokenAmount,
address payable sender,
address payable taker,
bytes calldata data
)
external
@ -40,6 +42,14 @@ contract TestFillQuoteTransformerHost is
inputToken.mint(address(this), inputTokenAmount);
}
// Have to make this call externally because transformers aren't payable.
this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
this.rawExecuteTransform(
transformer,
IERC20Transformer.TransformContext({
callDataHash: bytes32(0),
sender: sender,
taker: taker,
data: data
})
);
}
}

View File

@ -20,7 +20,6 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/ZeroEx.sol";
import "../src/features/IBootstrap.sol";
import "../src/migrations/FullMigration.sol";

View File

@ -20,7 +20,7 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/ZeroEx.sol";
import "../src/features/IBootstrap.sol";
import "../src/features/IBootstrapFeature.sol";
import "../src/migrations/InitialMigration.sol";
@ -34,7 +34,7 @@ contract TestInitialMigration is
constructor(address deployer) public InitialMigration(deployer) {}
function callBootstrap(ZeroEx zeroEx) external {
IBootstrap(address(zeroEx)).bootstrap(address(this), new bytes(0));
IBootstrapFeature(address(zeroEx)).bootstrap(address(this), new bytes(0));
}
function bootstrap(address owner, BootstrapFeatures memory features)
@ -45,7 +45,7 @@ contract TestInitialMigration is
success = InitialMigration.bootstrap(owner, features);
// Snoop the bootstrap feature contract.
bootstrapFeature = ZeroEx(address(uint160(address(this))))
.getFunctionImplementation(IBootstrap.bootstrap.selector);
.getFunctionImplementation(IBootstrapFeature.bootstrap.selector);
}
function die(address payable ethRecipient) public override {

View File

@ -19,11 +19,12 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/features/TransformERC20.sol";
import "../src/features/TransformERC20Feature.sol";
import "../src/features/IMetaTransactionsFeature.sol";
contract TestMetaTransactionsTransformERC20Feature is
TransformERC20
TransformERC20Feature
{
event TransformERC20Called(
address sender,
@ -48,6 +49,49 @@ contract TestMetaTransactionsTransformERC20Feature is
revert('FAIL');
}
if (msg.value == 777) {
// Try to reenter `executeMetaTransaction()`
IMetaTransactionsFeature(address(this)).executeMetaTransaction(
IMetaTransactionsFeature.MetaTransactionData({
signer: address(0),
sender: address(0),
minGasPrice: 0,
maxGasPrice: 0,
expirationTimeSeconds: 0,
salt: 0,
callData: "",
value: 0,
feeToken: IERC20TokenV06(0),
feeAmount: 0
}),
""
);
}
if (msg.value == 888) {
// Try to reenter `batchExecuteMetaTransactions()`
IMetaTransactionsFeature.MetaTransactionData[] memory mtxs =
new IMetaTransactionsFeature.MetaTransactionData[](1);
bytes[] memory signatures = new bytes[](1);
mtxs[0] = IMetaTransactionsFeature.MetaTransactionData({
signer: address(0),
sender: address(0),
minGasPrice: 0,
maxGasPrice: 0,
expirationTimeSeconds: 0,
salt: 0,
callData: "",
value: 0,
feeToken: IERC20TokenV06(0),
feeAmount: 0
});
signatures[0] = "";
IMetaTransactionsFeature(address(this)).batchExecuteMetaTransactions(
mtxs,
signatures
);
}
emit TransformERC20Called(
msg.sender,
msg.value,

View File

@ -20,7 +20,7 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/migrations/LibMigrate.sol";
import "../src/features/IOwnable.sol";
import "../src/features/IOwnableFeature.sol";
contract TestMigrator {
@ -32,7 +32,7 @@ contract TestMigrator {
function succeedingMigrate() external returns (bytes4 success) {
emit TestMigrateCalled(
msg.data,
IOwnable(address(this)).owner()
IOwnableFeature(address(this)).owner()
);
return LibMigrate.MIGRATE_SUCCESS;
}
@ -40,7 +40,7 @@ contract TestMigrator {
function failingMigrate() external returns (bytes4 success) {
emit TestMigrateCalled(
msg.data,
IOwnable(address(this)).owner()
IOwnableFeature(address(this)).owner()
);
return 0xdeadbeef;
}

View File

@ -40,28 +40,26 @@ contract TestMintTokenERC20Transformer is
address context,
address caller,
bytes32 callDataHash,
address sender,
address taker,
bytes data,
uint256 inputTokenBalance,
uint256 ethBalance
);
function transform(
bytes32 callDataHash,
address payable taker,
bytes calldata data_
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)
{
TransformData memory data = abi.decode(data_, (TransformData));
TransformData memory data = abi.decode(context.data, (TransformData));
emit MintTransform(
address(this),
msg.sender,
callDataHash,
taker,
data_,
context.callDataHash,
context.sender,
context.taker,
context.data,
data.inputToken.balanceOf(address(this)),
address(this).balance
);
@ -69,14 +67,14 @@ contract TestMintTokenERC20Transformer is
data.inputToken.transfer(address(0), data.burnAmount);
// Mint output tokens.
if (LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) {
taker.transfer(data.mintAmount);
context.taker.transfer(data.mintAmount);
} else {
data.outputToken.mint(
taker,
context.taker,
data.mintAmount
);
// Burn fees from output.
data.outputToken.burn(taker, data.feeAmount);
data.outputToken.burn(context.taker, data.feeAmount);
}
return LibERC20Transformer.TRANSFORMER_SUCCESS;
}

View File

@ -19,10 +19,10 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/features/TokenSpender.sol";
import "../src/features/TokenSpenderFeature.sol";
contract TestTokenSpender is
TokenSpender
TokenSpenderFeature
{
modifier onlySelf() override {
_;

View File

@ -19,18 +19,12 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/features/TransformERC20.sol";
import "../src/features/TransformERC20Feature.sol";
contract TestTransformERC20 is
TransformERC20
TransformERC20Feature
{
// solhint-disable no-empty-blocks
constructor()
TransformERC20()
public
{}
modifier onlySelf() override {
_;
}

View File

@ -20,17 +20,15 @@ pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "../src/transformers/Transformer.sol";
import "../src/transformers/IERC20Transformer.sol";
import "../src/transformers/LibERC20Transformer.sol";
contract TestTransformerBase is
IERC20Transformer,
Transformer
{
function transform(
bytes32,
address payable,
bytes calldata
)
function transform(TransformContext calldata context)
external
override
returns (bytes4 success)

View File

@ -24,10 +24,15 @@ import "../src/transformers/LibERC20Transformer.sol";
contract TestTransformerDeployerTransformer {
uint256 public constant CONSTRUCTOR_FAIL_VALUE = 3333;
address payable public immutable deployer;
constructor() public payable {
deployer = msg.sender;
require(
msg.value != CONSTRUCTOR_FAIL_VALUE,
"TestTransformerDeployerTransformer/CONSTRUCTOR_FAIL"
);
}
modifier onlyDeployer() {
@ -35,11 +40,11 @@ contract TestTransformerDeployerTransformer {
_;
}
function die()
function die(address payable ethRecipient)
external
onlyDeployer
{
selfdestruct(deployer);
selfdestruct(ethRecipient);
}
function isDeployedByDeployer(uint32 nonce)

View File

@ -32,18 +32,14 @@ contract TestTransformerHost {
function rawExecuteTransform(
IERC20Transformer transformer,
bytes32 callDataHash,
address taker,
bytes calldata data
IERC20Transformer.TransformContext calldata context
)
external
{
(bool _success, bytes memory resultData) =
address(transformer).delegatecall(abi.encodeWithSelector(
transformer.transform.selector,
callDataHash,
taker,
data
context
));
if (!_success) {
resultData.rrevert();

View File

@ -48,6 +48,14 @@ contract TestWethTransformerHost is
_weth.deposit{value: wethAmount}();
}
// Have to make this call externally because transformers aren't payable.
this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
this.rawExecuteTransform(
transformer,
IERC20Transformer.TransformContext({
callDataHash: bytes32(0),
sender: msg.sender,
taker: msg.sender,
data: data
})
);
}
}

View File

@ -39,9 +39,9 @@
"publish:private": "yarn build && gitpkg publish"
},
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions,BridgeAdapter",
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter",
"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|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@ -13,18 +13,19 @@ import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json'
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
import * as IOwnable from '../generated-artifacts/IOwnable.json';
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json';
import * as ISimpleFunctionRegistryFeature from '../generated-artifacts/ISimpleFunctionRegistryFeature.json';
import * as ITokenSpenderFeature from '../generated-artifacts/ITokenSpenderFeature.json';
import * as ITransformERC20Feature from '../generated-artifacts/ITransformERC20Feature.json';
import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
import * as MetaTransactions from '../generated-artifacts/MetaTransactions.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidator from '../generated-artifacts/SignatureValidator.json';
import * as SimpleFunctionRegistry from '../generated-artifacts/SimpleFunctionRegistry.json';
import * as TokenSpender from '../generated-artifacts/TokenSpender.json';
import * as TransformERC20 from '../generated-artifacts/TransformERC20.json';
import * as SignatureValidatorFeature from '../generated-artifacts/SignatureValidatorFeature.json';
import * as SimpleFunctionRegistryFeature from '../generated-artifacts/SimpleFunctionRegistryFeature.json';
import * as TokenSpenderFeature from '../generated-artifacts/TokenSpenderFeature.json';
import * as TransformERC20Feature from '../generated-artifacts/TransformERC20Feature.json';
import * as WethTransformer from '../generated-artifacts/WethTransformer.json';
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
export const artifacts = {
@ -35,19 +36,20 @@ export const artifacts = {
IFlashWallet: IFlashWallet as ContractArtifact,
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
IERC20Transformer: IERC20Transformer as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
ITokenSpender: ITokenSpender as ContractArtifact,
ITransformERC20: ITransformERC20 as ContractArtifact,
IOwnableFeature: IOwnableFeature as ContractArtifact,
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
WethTransformer: WethTransformer as ContractArtifact,
Ownable: Ownable as ContractArtifact,
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
TransformERC20: TransformERC20 as ContractArtifact,
TokenSpender: TokenSpender as ContractArtifact,
OwnableFeature: OwnableFeature as ContractArtifact,
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact,
SignatureValidator: SignatureValidator as ContractArtifact,
MetaTransactions: MetaTransactions as ContractArtifact,
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
};

View File

@ -36,13 +36,14 @@ export {
AffiliateFeeTransformerContract,
BridgeAdapterContract,
FillQuoteTransformerContract,
IOwnableContract,
IOwnableEvents,
ISimpleFunctionRegistryContract,
ISimpleFunctionRegistryEvents,
ITokenSpenderContract,
ITransformERC20Contract,
IOwnableFeatureContract,
IOwnableFeatureEvents,
ISimpleFunctionRegistryFeatureContract,
ISimpleFunctionRegistryFeatureEvents,
ITokenSpenderFeatureContract,
ITransformERC20FeatureContract,
IZeroExContract,
LogMetadataTransformerContract,
PayTakerTransformerContract,
WethTransformerContract,
ZeroExContract,

View File

@ -6,12 +6,13 @@ import { artifacts } from './artifacts';
import {
FullMigrationContract,
InitialMigrationContract,
MetaTransactionsContract,
OwnableContract,
SignatureValidatorContract,
SimpleFunctionRegistryContract,
TokenSpenderContract,
TransformERC20Contract,
IZeroExContract,
MetaTransactionsFeatureContract,
OwnableFeatureContract,
SignatureValidatorFeatureContract,
SimpleFunctionRegistryFeatureContract,
TokenSpenderFeatureContract,
TransformERC20FeatureContract,
ZeroExContract,
} from './wrappers';
@ -36,16 +37,20 @@ export async function deployBootstrapFeaturesAsync(
return {
registry:
features.registry ||
(await SimpleFunctionRegistryContract.deployFrom0xArtifactAsync(
artifacts.SimpleFunctionRegistry,
(await SimpleFunctionRegistryFeatureContract.deployFrom0xArtifactAsync(
artifacts.SimpleFunctionRegistryFeature,
provider,
txDefaults,
artifacts,
)).address,
ownable:
features.ownable ||
(await OwnableContract.deployFrom0xArtifactAsync(artifacts.Ownable, provider, txDefaults, artifacts))
.address,
(await OwnableFeatureContract.deployFrom0xArtifactAsync(
artifacts.OwnableFeature,
provider,
txDefaults,
artifacts,
)).address,
};
}
@ -107,32 +112,32 @@ export async function deployFullFeaturesAsync(
...(await deployBootstrapFeaturesAsync(provider, txDefaults)),
tokenSpender:
features.tokenSpender ||
(await TokenSpenderContract.deployFrom0xArtifactAsync(
artifacts.TokenSpender,
(await TokenSpenderFeatureContract.deployFrom0xArtifactAsync(
artifacts.TokenSpenderFeature,
provider,
txDefaults,
artifacts,
)).address,
transformERC20:
features.transformERC20 ||
(await TransformERC20Contract.deployFrom0xArtifactAsync(
artifacts.TransformERC20,
(await TransformERC20FeatureContract.deployFrom0xArtifactAsync(
artifacts.TransformERC20Feature,
provider,
txDefaults,
artifacts,
)).address,
signatureValidator:
features.signatureValidator ||
(await SignatureValidatorContract.deployFrom0xArtifactAsync(
artifacts.SignatureValidator,
(await SignatureValidatorFeatureContract.deployFrom0xArtifactAsync(
artifacts.SignatureValidatorFeature,
provider,
txDefaults,
artifacts,
)).address,
metaTransactions:
features.metaTransactions ||
(await MetaTransactionsContract.deployFrom0xArtifactAsync(
artifacts.MetaTransactions,
(await MetaTransactionsFeatureContract.deployFrom0xArtifactAsync(
artifacts.MetaTransactionsFeature,
provider,
txDefaults,
artifacts,
@ -150,7 +155,7 @@ export async function fullMigrateAsync(
txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {},
): Promise<ZeroExContract> {
): Promise<IZeroExContract> {
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
artifacts.FullMigration,
provider,
@ -171,5 +176,5 @@ export async function fullMigrateAsync(
...opts,
};
await migrator.initializeZeroEx(owner, zeroEx.address, _features, _opts).awaitTransactionSuccessAsync();
return zeroEx;
return new IZeroExContract(zeroEx.address, provider, txDefaults);
}

View File

@ -10,18 +10,19 @@ export * from '../generated-wrappers/full_migration';
export * from '../generated-wrappers/i_allowance_target';
export * from '../generated-wrappers/i_erc20_transformer';
export * from '../generated-wrappers/i_flash_wallet';
export * from '../generated-wrappers/i_ownable';
export * from '../generated-wrappers/i_simple_function_registry';
export * from '../generated-wrappers/i_token_spender';
export * from '../generated-wrappers/i_transform_erc20';
export * from '../generated-wrappers/i_ownable_feature';
export * from '../generated-wrappers/i_simple_function_registry_feature';
export * from '../generated-wrappers/i_token_spender_feature';
export * from '../generated-wrappers/i_transform_erc20_feature';
export * from '../generated-wrappers/i_zero_ex';
export * from '../generated-wrappers/initial_migration';
export * from '../generated-wrappers/meta_transactions';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/log_metadata_transformer';
export * from '../generated-wrappers/meta_transactions_feature';
export * from '../generated-wrappers/ownable_feature';
export * from '../generated-wrappers/pay_taker_transformer';
export * from '../generated-wrappers/signature_validator';
export * from '../generated-wrappers/simple_function_registry';
export * from '../generated-wrappers/token_spender';
export * from '../generated-wrappers/transform_erc20';
export * from '../generated-wrappers/signature_validator_feature';
export * from '../generated-wrappers/simple_function_registry_feature';
export * from '../generated-wrappers/token_spender_feature';
export * from '../generated-wrappers/transform_erc20_feature';
export * from '../generated-wrappers/weth_transformer';
export * from '../generated-wrappers/zero_ex';

View File

@ -7,15 +7,16 @@ import { ContractArtifact } from 'ethereum-types';
import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json';
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
import * as BootstrapFeature from '../test/generated-artifacts/BootstrapFeature.json';
import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json';
import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentrancyGuard.json';
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json';
import * as IBootstrapFeature from '../test/generated-artifacts/IBootstrapFeature.json';
import * as IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.json';
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
@ -23,14 +24,14 @@ import * as IExchange from '../test/generated-artifacts/IExchange.json';
import * as IFeature from '../test/generated-artifacts/IFeature.json';
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
import * as IMetaTransactions from '../test/generated-artifacts/IMetaTransactions.json';
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
import * as ISignatureValidator from '../test/generated-artifacts/ISignatureValidator.json';
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
import * as ISignatureValidatorFeature from '../test/generated-artifacts/ISignatureValidatorFeature.json';
import * as ISimpleFunctionRegistryFeature from '../test/generated-artifacts/ISimpleFunctionRegistryFeature.json';
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json';
import * as ITransformERC20 from '../test/generated-artifacts/ITransformERC20.json';
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
@ -42,6 +43,7 @@ import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRic
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
import * as LibReentrancyGuardStorage from '../test/generated-artifacts/LibReentrancyGuardStorage.json';
import * as LibSignatureRichErrors from '../test/generated-artifacts/LibSignatureRichErrors.json';
import * as LibSignedCallData from '../test/generated-artifacts/LibSignedCallData.json';
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
@ -52,20 +54,22 @@ import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpe
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
import * as MetaTransactions from '../test/generated-artifacts/MetaTransactions.json';
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapterAddresses.json';
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json';
import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json';
import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json';
import * as MixinOasis from '../test/generated-artifacts/MixinOasis.json';
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
import * as Ownable from '../test/generated-artifacts/Ownable.json';
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidator from '../test/generated-artifacts/SignatureValidator.json';
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
import * as SignatureValidatorFeature from '../test/generated-artifacts/SignatureValidatorFeature.json';
import * as SimpleFunctionRegistryFeature from '../test/generated-artifacts/SimpleFunctionRegistryFeature.json';
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
@ -88,26 +92,15 @@ import * as TestTransformerHost from '../test/generated-artifacts/TestTransforme
import * as TestWeth from '../test/generated-artifacts/TestWeth.json';
import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json';
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json';
import * as TokenSpenderFeature from '../test/generated-artifacts/TokenSpenderFeature.json';
import * as Transformer from '../test/generated-artifacts/Transformer.json';
import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json';
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
export const artifacts = {
IZeroEx: IZeroEx as ContractArtifact,
ZeroEx: ZeroEx as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
MixinAdapterAddresses: MixinAdapterAddresses as ContractArtifact,
MixinBalancer: MixinBalancer as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact,
MixinKyber: MixinKyber as ContractArtifact,
MixinMStable: MixinMStable as ContractArtifact,
MixinOasis: MixinOasis as ContractArtifact,
MixinUniswap: MixinUniswap as ContractArtifact,
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
@ -122,24 +115,25 @@ export const artifacts = {
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
IFlashWallet: IFlashWallet as ContractArtifact,
TransformerDeployer: TransformerDeployer as ContractArtifact,
Bootstrap: Bootstrap as ContractArtifact,
IBootstrap: IBootstrap as ContractArtifact,
BootstrapFeature: BootstrapFeature as ContractArtifact,
IBootstrapFeature: IBootstrapFeature as ContractArtifact,
IFeature: IFeature as ContractArtifact,
IMetaTransactions: IMetaTransactions as ContractArtifact,
IOwnable: IOwnable as ContractArtifact,
ISignatureValidator: ISignatureValidator as ContractArtifact,
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
ITokenSpender: ITokenSpender as ContractArtifact,
ITransformERC20: ITransformERC20 as ContractArtifact,
MetaTransactions: MetaTransactions as ContractArtifact,
Ownable: Ownable as ContractArtifact,
SignatureValidator: SignatureValidator as ContractArtifact,
SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact,
TokenSpender: TokenSpender as ContractArtifact,
TransformERC20: TransformERC20 as ContractArtifact,
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
IOwnableFeature: IOwnableFeature as ContractArtifact,
ISignatureValidatorFeature: ISignatureValidatorFeature as ContractArtifact,
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
OwnableFeature: OwnableFeature as ContractArtifact,
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
LibSignedCallData: LibSignedCallData as ContractArtifact,
FixinCommon: FixinCommon as ContractArtifact,
FixinEIP712: FixinEIP712 as ContractArtifact,
FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact,
FullMigration: FullMigration as ContractArtifact,
InitialMigration: InitialMigration as ContractArtifact,
LibBootstrap: LibBootstrap as ContractArtifact,
@ -147,6 +141,7 @@ export const artifacts = {
LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
LibProxyStorage: LibProxyStorage as ContractArtifact,
LibReentrancyGuardStorage: LibReentrancyGuardStorage as ContractArtifact,
LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact,
LibStorage: LibStorage as ContractArtifact,
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
@ -155,9 +150,22 @@ export const artifacts = {
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
IERC20Transformer: IERC20Transformer as ContractArtifact,
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
Transformer: Transformer as ContractArtifact,
WethTransformer: WethTransformer as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
MixinAdapterAddresses: MixinAdapterAddresses as ContractArtifact,
MixinBalancer: MixinBalancer as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact,
MixinKyber: MixinKyber as ContractArtifact,
MixinMStable: MixinMStable as ContractArtifact,
MixinMooniswap: MixinMooniswap as ContractArtifact,
MixinOasis: MixinOasis as ContractArtifact,
MixinUniswap: MixinUniswap as ContractArtifact,
MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
IExchange: IExchange as ContractArtifact,
IGasToken: IGasToken as ContractArtifact,

View File

@ -12,12 +12,12 @@ import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/
import * as _ from 'lodash';
import { generateCallDataSignature, signCallData } from '../../src/signed_call_data';
import { MetaTransactionsContract, ZeroExContract } from '../../src/wrappers';
import { IZeroExContract, MetaTransactionsFeatureContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import {
ITokenSpenderContract,
ITokenSpenderFeatureContract,
TestMetaTransactionsTransformERC20FeatureContract,
TestMetaTransactionsTransformERC20FeatureEvents,
TestMintableERC20TokenContract,
@ -29,13 +29,17 @@ blockchainTests.resets('MetaTransactions feature', env => {
let owner: string;
let sender: string;
let signers: string[];
let zeroEx: ZeroExContract;
let feature: MetaTransactionsContract;
let zeroEx: IZeroExContract;
let feature: MetaTransactionsFeatureContract;
let feeToken: TestMintableERC20TokenContract;
let transformERC20Feature: TestMetaTransactionsTransformERC20FeatureContract;
let allowanceTarget: string;
const MAX_FEE_AMOUNT = new BigNumber('1e18');
const TRANSFORM_ERC20_FAILING_VALUE = new BigNumber(666);
const TRANSFORM_ERC20_REENTER_VALUE = new BigNumber(777);
const TRANSFORM_ERC20_BATCH_REENTER_VALUE = new BigNumber(888);
const REENTRANCY_FLAG_MTX = 0x1;
before(async () => {
[owner, sender, ...signers] = await env.getAccountAddressesAsync();
@ -48,14 +52,19 @@ blockchainTests.resets('MetaTransactions feature', env => {
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
transformERC20: transformERC20Feature.address,
});
feature = new MetaTransactionsContract(zeroEx.address, env.provider, { ...env.txDefaults, from: sender }, abis);
feature = new MetaTransactionsFeatureContract(
zeroEx.address,
env.provider,
{ ...env.txDefaults, from: sender },
abis,
);
feeToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestMintableERC20Token,
env.provider,
env.txDefaults,
{},
);
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
allowanceTarget = await new ITokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults)
.getAllowanceTarget()
.callAsync();
// Fund signers with fee tokens.
@ -263,7 +272,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
it('fails if the translated call fails', async () => {
const args = getRandomTransformERC20Args();
const mtx = getRandomMetaTransaction({
value: new BigNumber(666),
value: new BigNumber(TRANSFORM_ERC20_FAILING_VALUE),
callData: transformERC20Feature
.transformERC20(
args.inputToken,
@ -297,7 +306,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
mtxHash,
actualCallData,
new StringRevertError('FAIL').toString(),
new StringRevertError('FAIL').encode(),
),
);
});
@ -465,7 +474,139 @@ blockchainTests.resets('MetaTransactions feature', env => {
mtxHash,
signers[0],
signature,
).toString(),
).encode(),
),
);
});
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(),
),
);
});
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(),
),
);
});
@ -526,6 +667,102 @@ blockchainTests.resets('MetaTransactions feature', env => {
new ZeroExRevertErrors.MetaTransactions.MetaTransactionAlreadyExecutedError(mtxHash, block),
);
});
it('fails if a meta-transaction fails', async () => {
const args = getRandomTransformERC20Args();
const mtx = getRandomMetaTransaction({
value: new BigNumber(TRANSFORM_ERC20_FAILING_VALUE),
callData: transformERC20Feature
.transformERC20(
args.inputToken,
args.outputToken,
args.inputTokenAmount,
args.minOutputTokenAmount,
args.transformations,
)
.getABIEncodedTransactionData(),
});
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
const signature = await signMetaTransactionAsync(mtx);
const callOpts = {
gasPrice: mtx.minGasPrice,
value: mtx.value,
};
const tx = feature.batchExecuteMetaTransactions([mtx], [signature]).callAsync(callOpts);
return expect(tx).to.revertWith(
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
mtxHash,
undefined,
new StringRevertError('FAIL').encode(),
),
);
});
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.batchExecuteMetaTransactions([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.batchExecuteMetaTransactions([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('getMetaTransactionExecutedBlock()', () => {

View File

@ -3,12 +3,12 @@ import { hexUtils, OwnableRevertErrors, StringRevertError, ZeroExRevertErrors }
import { artifacts } from '../artifacts';
import { initialMigrateAsync } from '../utils/migration';
import { IOwnableContract, IOwnableEvents, TestMigratorContract, TestMigratorEvents } from '../wrappers';
import { IOwnableFeatureContract, IOwnableFeatureEvents, TestMigratorContract, TestMigratorEvents } from '../wrappers';
blockchainTests.resets('Ownable feature', env => {
const notOwner = randomAddress();
let owner: string;
let ownable: IOwnableContract;
let ownable: IOwnableFeatureContract;
let testMigrator: TestMigratorContract;
let succeedingMigrateFnCallData: string;
let failingMigrateFnCallData: string;
@ -19,7 +19,7 @@ blockchainTests.resets('Ownable feature', env => {
[owner] = await env.getAccountAddressesAsync();
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
const zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
ownable = new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults);
testMigrator = await TestMigratorContract.deployFrom0xArtifactAsync(
artifacts.TestMigrator,
env.provider,
@ -49,7 +49,7 @@ blockchainTests.resets('Ownable feature', env => {
newOwner,
},
],
IOwnableEvents.OwnershipTransferred,
IOwnableFeatureEvents.OwnershipTransferred,
);
expect(await ownable.owner().callAsync()).to.eq(newOwner);
});
@ -102,7 +102,7 @@ blockchainTests.resets('Ownable feature', env => {
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Ownable.MigrateCallFailedError(
testMigrator.address,
new StringRevertError('OOPSIE').toString(),
new StringRevertError('OOPSIE').encode(),
),
);
});

View File

@ -5,7 +5,7 @@ import { hexUtils, ZeroExRevertErrors } from '@0x/utils';
import * as ethjs from 'ethereumjs-util';
import * as _ from 'lodash';
import { SignatureValidatorContract, ZeroExContract } from '../../src/wrappers';
import { IZeroExContract, SignatureValidatorFeatureContract } from '../../src/wrappers';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
@ -14,13 +14,13 @@ const { NULL_BYTES } = constants;
blockchainTests.resets('SignatureValidator feature', env => {
let owner: string;
let signers: string[];
let zeroEx: ZeroExContract;
let feature: SignatureValidatorContract;
let zeroEx: IZeroExContract;
let feature: SignatureValidatorFeatureContract;
before(async () => {
[owner, ...signers] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults);
feature = new SignatureValidatorContract(zeroEx.address, env.provider, env.txDefaults, abis);
feature = new SignatureValidatorFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis);
});
describe('validateHashSignature()', () => {

View File

@ -5,8 +5,8 @@ import { ZeroExContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { initialMigrateAsync } from '../utils/migration';
import {
ISimpleFunctionRegistryContract,
ISimpleFunctionRegistryEvents,
ISimpleFunctionRegistryFeatureContract,
ISimpleFunctionRegistryFeatureEvents,
ITestSimpleFunctionRegistryFeatureContract,
TestSimpleFunctionRegistryFeatureImpl1Contract,
TestSimpleFunctionRegistryFeatureImpl2Contract,
@ -17,7 +17,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
const notOwner = randomAddress();
let owner: string;
let zeroEx: ZeroExContract;
let registry: ISimpleFunctionRegistryContract;
let registry: ISimpleFunctionRegistryFeatureContract;
let testFnSelector: string;
let testFeature: ITestSimpleFunctionRegistryFeatureContract;
let testFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1Contract;
@ -26,7 +26,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
before(async () => {
[owner] = await env.getAccountAddressesAsync();
zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, {
registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, {
...env.txDefaults,
from: owner,
});
@ -75,7 +75,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
verifyEventsFromLogs(
logs,
[{ selector: testFnSelector, oldImpl: NULL_ADDRESS, newImpl: testFeatureImpl1.address }],
ISimpleFunctionRegistryEvents.ProxyFunctionUpdated,
ISimpleFunctionRegistryFeatureEvents.ProxyFunctionUpdated,
);
const r = await testFeature.testFn().callAsync();
expect(r).to.bignumber.eq(1337);
@ -117,7 +117,7 @@ blockchainTests.resets('SimpleFunctionRegistry feature', env => {
verifyEventsFromLogs(
logs,
[{ selector: testFnSelector, oldImpl: testFeatureImpl2.address, newImpl: NULL_ADDRESS }],
ISimpleFunctionRegistryEvents.ProxyFunctionUpdated,
ISimpleFunctionRegistryFeatureEvents.ProxyFunctionUpdated,
);
const rollbackLength = await registry.getRollbackLength(testFnSelector).callAsync();
expect(rollbackLength).to.bignumber.eq(0);

View File

@ -7,29 +7,29 @@ import {
} from '@0x/contracts-test-utils';
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import { TokenSpenderContract, ZeroExContract } from '../../src/wrappers';
import { IZeroExContract, TokenSpenderFeatureContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import { TestTokenSpenderERC20TokenContract, TestTokenSpenderERC20TokenEvents } from '../wrappers';
blockchainTests.resets('TokenSpender feature', env => {
let zeroEx: ZeroExContract;
let feature: TokenSpenderContract;
let zeroEx: IZeroExContract;
let feature: TokenSpenderFeatureContract;
let token: TestTokenSpenderERC20TokenContract;
let allowanceTarget: string;
before(async () => {
const [owner] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
tokenSpender: (await TokenSpenderContract.deployFrom0xArtifactAsync(
tokenSpender: (await TokenSpenderFeatureContract.deployFrom0xArtifactAsync(
artifacts.TestTokenSpender,
env.provider,
env.txDefaults,
artifacts,
)).address,
});
feature = new TokenSpenderContract(zeroEx.address, env.provider, env.txDefaults, abis);
feature = new TokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis);
token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.TestTokenSpenderERC20Token,
env.provider,
@ -98,7 +98,7 @@ blockchainTests.resets('TokenSpender feature', env => {
tokenFrom,
tokenTo,
tokenAmount,
new StringRevertError('TestTokenSpenderERC20Token/Revert').toString(),
new StringRevertError('TestTokenSpenderERC20Token/Revert').encode(),
);
return expect(tx).to.revertWith(expectedError);
});

View File

@ -14,21 +14,21 @@ import { DecodedLogEntry } from 'ethereum-types';
import * as ethjs from 'ethereumjs-util';
import { generateCallDataHashSignature, signCallData } from '../../src/signed_call_data';
import { TransformERC20Contract, ZeroExContract } from '../../src/wrappers';
import { IZeroExContract, TransformERC20FeatureContract } from '../../src/wrappers';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import {
FlashWalletContract,
ITokenSpenderContract,
ITokenSpenderFeatureContract,
TestMintableERC20TokenContract,
TestMintTokenERC20TransformerContract,
TestMintTokenERC20TransformerEvents,
TestMintTokenERC20TransformerMintTransformEventArgs,
TransformERC20Events,
TransformERC20FeatureEvents,
} from '../wrappers';
const { NULL_BYTES, NULL_BYTES32 } = constants;
const { NULL_ADDRESS, NULL_BYTES, NULL_BYTES32 } = constants;
type MintTokenTransformerEvent = DecodedLogEntry<TestMintTokenERC20TransformerMintTransformEventArgs>;
@ -37,20 +37,21 @@ blockchainTests.resets('TransformERC20 feature', env => {
const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey)));
let owner: string;
let taker: string;
let sender: string;
let transformerDeployer: string;
let zeroEx: ZeroExContract;
let feature: TransformERC20Contract;
let zeroEx: IZeroExContract;
let feature: TransformERC20FeatureContract;
let wallet: FlashWalletContract;
let allowanceTarget: string;
before(async () => {
[owner, taker, transformerDeployer] = await env.getAccountAddressesAsync();
[owner, taker, sender, transformerDeployer] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(
owner,
env.provider,
env.txDefaults,
{
transformERC20: (await TransformERC20Contract.deployFrom0xArtifactAsync(
transformERC20: (await TransformERC20FeatureContract.deployFrom0xArtifactAsync(
artifacts.TestTransformERC20,
env.provider,
env.txDefaults,
@ -59,12 +60,17 @@ blockchainTests.resets('TransformERC20 feature', env => {
},
{ transformerDeployer },
);
feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis);
feature = new TransformERC20FeatureContract(
zeroEx.address,
env.provider,
{ ...env.txDefaults, from: sender },
abis,
);
wallet = new FlashWalletContract(await feature.getTransformWallet().callAsync(), env.provider, env.txDefaults);
allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults)
allowanceTarget = await new ITokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults)
.getAllowanceTarget()
.callAsync();
await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync();
await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync({ from: owner });
});
const { MAX_UINT256, ZERO_AMOUNT } = constants;
@ -73,7 +79,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
it('createTransformWallet() replaces the current wallet', async () => {
const newWalletAddress = await feature.createTransformWallet().callAsync({ from: owner });
expect(newWalletAddress).to.not.eq(wallet.address);
await feature.createTransformWallet().awaitTransactionSuccessAsync();
await feature.createTransformWallet().awaitTransactionSuccessAsync({ from: owner });
return expect(feature.getTransformWallet().callAsync()).to.eventually.eq(newWalletAddress);
});
@ -98,7 +104,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
verifyEventsFromLogs(
receipt.logs,
[{ transformerDeployer: newDeployer }],
TransformERC20Events.TransformerDeployerUpdated,
TransformERC20FeatureEvents.TransformerDeployerUpdated,
);
const actualDeployer = await feature.getTransformerDeployer().callAsync();
expect(actualDeployer).to.eq(newDeployer);
@ -121,7 +127,11 @@ blockchainTests.resets('TransformERC20 feature', env => {
it('owner can set the quote signer with `setQuoteSigner()`', async () => {
const newSigner = randomAddress();
const receipt = await feature.setQuoteSigner(newSigner).awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(receipt.logs, [{ quoteSigner: newSigner }], TransformERC20Events.QuoteSignerUpdated);
verifyEventsFromLogs(
receipt.logs,
[{ quoteSigner: newSigner }],
TransformERC20FeatureEvents.QuoteSignerUpdated,
);
const actualSigner = await feature.getQuoteSigner().callAsync();
expect(actualSigner).to.eq(newSigner);
});
@ -258,14 +268,15 @@ blockchainTests.resets('TransformERC20 feature', env => {
outputToken: outputToken.address,
},
],
TransformERC20Events.TransformedERC20,
TransformERC20FeatureEvents.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash: NULL_BYTES32,
sender,
taker,
callDataHash: NULL_BYTES32,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
@ -314,14 +325,15 @@ blockchainTests.resets('TransformERC20 feature', env => {
outputToken: ETH_TOKEN_ADDRESS,
},
],
TransformERC20Events.TransformedERC20,
TransformERC20FeatureEvents.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash: NULL_BYTES32,
taker,
sender,
callDataHash: NULL_BYTES32,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
@ -373,14 +385,15 @@ blockchainTests.resets('TransformERC20 feature', env => {
outputToken: outputToken.address,
},
],
TransformERC20Events.TransformedERC20,
TransformERC20FeatureEvents.TransformedERC20,
);
verifyEventsFromLogs(
receipt.logs,
[
{
callDataHash: NULL_BYTES32,
sender,
taker,
callDataHash: NULL_BYTES32,
context: wallet.address,
caller: zeroEx.address,
data: transformation.data,
@ -496,8 +509,9 @@ blockchainTests.resets('TransformERC20 feature', env => {
receipt.logs,
[
{
callDataHash: NULL_BYTES32,
sender,
taker,
callDataHash: NULL_BYTES32,
context: wallet.address,
caller: zeroEx.address,
data: transformations[0].data,
@ -505,8 +519,9 @@ blockchainTests.resets('TransformERC20 feature', env => {
ethBalance: callValue,
},
{
callDataHash: NULL_BYTES32,
sender,
taker,
callDataHash: NULL_BYTES32,
context: wallet.address,
caller: zeroEx.address,
data: transformations[1].data,
@ -672,6 +687,37 @@ blockchainTests.resets('TransformERC20 feature', env => {
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
});
it('passes the calldata hash to transformer when no quote signer configured', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');
await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync();
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
const minOutputTokenAmount = getRandomInteger(1, '1e18');
const outputTokenMintAmount = minOutputTokenAmount;
const callValue = getRandomInteger(1, '1e18');
const transformation = createMintTokenTransformation({
outputTokenMintAmount,
inputTokenBurnAmunt: inputTokenAmount,
});
const bakedCall = feature.transformERC20(
inputToken.address,
outputToken.address,
inputTokenAmount,
minOutputTokenAmount,
[transformation],
);
const callData = bakedCall.getABIEncodedTransactionData();
await feature.setQuoteSigner(NULL_ADDRESS).awaitTransactionSuccessAsync({ from: owner });
const receipt = await bakedCall.awaitTransactionSuccessAsync({
from: taker,
value: callValue,
data: callData,
});
const { callDataHash: actualCallDataHash } = (receipt.logs[0] as MintTokenTransformerEvent).args;
expect(actualCallDataHash).to.eq(hexUtils.hash(callData));
});
it('passes empty calldata hash to transformer with improperly signed calldata', async () => {
const startingOutputTokenBalance = getRandomInteger(0, '100e18');
const startingInputTokenBalance = getRandomInteger(0, '100e18');

View File

@ -127,7 +127,7 @@ blockchainTests.resets('FlashWallet', env => {
callTarget.address,
REVERTING_DATA,
constants.ZERO_AMOUNT,
new StringRevertError('TestCallTarget/REVERT').toString(),
new StringRevertError('TestCallTarget/REVERT'),
),
);
});
@ -203,7 +203,7 @@ blockchainTests.resets('FlashWallet', env => {
wallet.address,
callTarget.address,
REVERTING_DATA,
new StringRevertError('TestCallTarget/REVERT').toString(),
new StringRevertError('TestCallTarget/REVERT'),
),
);
});

View File

@ -9,11 +9,11 @@ import { abis } from './utils/abis';
import { deployFullFeaturesAsync, FullFeatures } from './utils/migration';
import {
AllowanceTargetContract,
IMetaTransactionsContract,
IOwnableContract,
ISignatureValidatorContract,
ITokenSpenderContract,
ITransformERC20Contract,
IMetaTransactionsFeatureContract,
IOwnableFeatureContract,
ISignatureValidatorFeatureContract,
ITokenSpenderFeatureContract,
ITransformERC20FeatureContract,
TestFullMigrationContract,
ZeroExContract,
} from './wrappers';
@ -50,7 +50,7 @@ blockchainTests.resets('Full migration', env => {
});
it('ZeroEx has the correct owner', async () => {
const ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
const ownable = new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults);
const actualOwner = await ownable.owner().callAsync();
expect(actualOwner).to.eq(owner);
});
@ -70,11 +70,11 @@ blockchainTests.resets('Full migration', env => {
const FEATURE_FNS = {
TokenSpender: {
contractType: ITokenSpenderContract,
contractType: ITokenSpenderFeatureContract,
fns: ['_spendERC20Tokens'],
},
TransformERC20: {
contractType: ITransformERC20Contract,
contractType: ITransformERC20FeatureContract,
fns: [
'transformERC20',
'_transformERC20',
@ -86,11 +86,11 @@ blockchainTests.resets('Full migration', env => {
],
},
SignatureValidator: {
contractType: ISignatureValidatorContract,
contractType: ISignatureValidatorFeatureContract,
fns: ['isValidHashSignature', 'validateHashSignature'],
},
MetaTransactions: {
contractType: IMetaTransactionsContract,
contractType: IMetaTransactionsFeatureContract,
fns: [
'executeMetaTransaction',
'batchExecuteMetaTransactions',
@ -181,7 +181,7 @@ blockchainTests.resets('Full migration', env => {
let allowanceTarget: AllowanceTargetContract;
before(async () => {
const contract = new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults);
const contract = new ITokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults);
allowanceTarget = new AllowanceTargetContract(
await contract.getAllowanceTarget().callAsync(),
env.provider,
@ -199,10 +199,10 @@ blockchainTests.resets('Full migration', env => {
});
describe('TransformERC20', () => {
let feature: ITransformERC20Contract;
let feature: ITransformERC20FeatureContract;
before(async () => {
feature = new ITransformERC20Contract(zeroEx.address, env.provider, env.txDefaults);
feature = new ITransformERC20FeatureContract(zeroEx.address, env.provider, env.txDefaults);
});
it('has the correct transformer deployer', async () => {

View File

@ -4,10 +4,10 @@ import { hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { BootstrapFeatures, deployBootstrapFeaturesAsync } from './utils/migration';
import {
IBootstrapContract,
IBootstrapFeatureContract,
InitialMigrationContract,
IOwnableContract,
SimpleFunctionRegistryContract,
IOwnableFeatureContract,
SimpleFunctionRegistryFeatureContract,
TestInitialMigrationContract,
ZeroExContract,
} from './wrappers';
@ -16,7 +16,7 @@ blockchainTests.resets('Initial migration', env => {
let owner: string;
let zeroEx: ZeroExContract;
let migrator: TestInitialMigrationContract;
let bootstrapFeature: IBootstrapContract;
let bootstrapFeature: IBootstrapFeatureContract;
let features: BootstrapFeatures;
before(async () => {
@ -29,7 +29,7 @@ blockchainTests.resets('Initial migration', env => {
artifacts,
env.txDefaults.from as string,
);
bootstrapFeature = new IBootstrapContract(
bootstrapFeature = new IBootstrapFeatureContract(
await migrator.bootstrapFeature().callAsync(),
env.provider,
env.txDefaults,
@ -82,10 +82,10 @@ blockchainTests.resets('Initial migration', env => {
});
describe('Ownable feature', () => {
let ownable: IOwnableContract;
let ownable: IOwnableFeatureContract;
before(async () => {
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
ownable = new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults);
});
it('has the correct owner', async () => {
@ -95,10 +95,10 @@ blockchainTests.resets('Initial migration', env => {
});
describe('SimpleFunctionRegistry feature', () => {
let registry: SimpleFunctionRegistryContract;
let registry: SimpleFunctionRegistryFeatureContract;
before(async () => {
registry = new SimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
registry = new SimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults);
});
it('_extendSelf() is deregistered', async () => {

View File

@ -59,6 +59,12 @@ blockchainTests.resets('TransformerDeployer', env => {
expect(await env.web3Wrapper.getBalanceInWeiAsync(targetAddress)).to.bignumber.eq(1);
});
it('reverts if constructor throws', async () => {
const CONSTRUCTOR_FAIL_VALUE = new BigNumber(3333);
const tx = deployer.deploy(deployBytes).callAsync({ value: CONSTRUCTOR_FAIL_VALUE, from: authority });
return expect(tx).to.revertWith('TransformerDeployer/DEPLOY_FAILED');
});
it('updates nonce', async () => {
expect(await deployer.nonce().callAsync()).to.bignumber.eq(1);
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
@ -82,6 +88,7 @@ blockchainTests.resets('TransformerDeployer', env => {
});
describe('kill()', () => {
const ethRecipient = randomAddress();
let target: TestTransformerDeployerTransformerContract;
before(async () => {
@ -90,14 +97,16 @@ blockchainTests.resets('TransformerDeployer', env => {
await deployer.deploy(deployBytes).awaitTransactionSuccessAsync({ from: authority });
});
it('authority cannot call', async () => {
it('non-authority cannot call', async () => {
const nonAuthority = randomAddress();
const tx = deployer.kill(target.address).callAsync({ from: nonAuthority });
const tx = deployer.kill(target.address, ethRecipient).callAsync({ from: nonAuthority });
return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(nonAuthority));
});
it('authority can kill a contract', async () => {
const receipt = await deployer.kill(target.address).awaitTransactionSuccessAsync({ from: authority });
const receipt = await deployer
.kill(target.address, ethRecipient)
.awaitTransactionSuccessAsync({ from: authority });
verifyEventsFromLogs(
receipt.logs,
[{ target: target.address, sender: authority }],

View File

@ -86,7 +86,12 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
sender: randomAddress(),
taker: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
expect(await getBalancesAsync(recipients[0])).to.deep.eq({
@ -112,7 +117,12 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
sender: randomAddress(),
taker: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
expect(await getBalancesAsync(recipients[0])).to.deep.eq({
@ -138,7 +148,12 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), randomAddress(), data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
sender: randomAddress(),
taker: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq({
tokenBalance: new BigNumber(1),

View File

@ -32,6 +32,8 @@ const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants;
blockchainTests.resets('FillQuoteTransformer', env => {
let maker: string;
let feeRecipient: string;
let sender: string;
let taker: string;
let exchange: TestFillQuoteTransformerExchangeContract;
let bridge: TestFillQuoteTransformerBridgeContract;
let transformer: FillQuoteTransformerContract;
@ -44,7 +46,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
const GAS_PRICE = 1337;
before(async () => {
[maker, feeRecipient] = await env.getAccountAddressesAsync();
[maker, feeRecipient, sender, taker] = await env.getAccountAddressesAsync();
exchange = await TestFillQuoteTransformerExchangeContract.deployFrom0xArtifactAsync(
artifacts.TestFillQuoteTransformerExchange,
env.provider,
@ -60,6 +62,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
balancerBridge: NULL_ADDRESS,
curveBridge: NULL_ADDRESS,
kyberBridge: NULL_ADDRESS,
mooniswapBridge: NULL_ADDRESS,
mStableBridge: NULL_ADDRESS,
oasisBridge: NULL_ADDRESS,
uniswapBridge: NULL_ADDRESS,
@ -92,7 +95,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
bridge = await TestFillQuoteTransformerBridgeContract.deployFrom0xArtifactAsync(
artifacts.TestFillQuoteTransformerBridge,
env.provider,
env.txDefaults,
{ ...env.txDefaults, from: sender },
artifacts,
);
[makerToken, takerToken, takerFeeToken] = await Promise.all(
@ -270,6 +273,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
signatures: [],
maxOrderFillAmounts: [],
fillAmount: MAX_UINT256,
refundReceiver: NULL_ADDRESS,
rfqtTakerAddress: NULL_ADDRESS,
...fields,
});
}
@ -313,6 +318,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -334,6 +341,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -358,6 +367,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -380,6 +391,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -404,6 +417,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -430,6 +445,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -462,6 +479,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -486,6 +505,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -511,6 +532,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
takerTokenBalance,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -535,6 +558,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
takerTokenBalance,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -564,6 +589,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -586,6 +613,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -608,6 +637,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -627,6 +658,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -640,6 +673,80 @@ blockchainTests.resets('FillQuoteTransformer', env => {
makerAssetBalance: qfr.makerAssetBought,
});
});
it('can refund unspent protocol fee to the `refundReceiver`', async () => {
const orders = _.times(2, () => createOrder());
const signatures = orders.map(() => encodeExchangeBehavior());
const qfr = getExpectedSellQuoteFillResults(orders);
const protocolFee = qfr.protocolFeePaid.plus(1);
const refundReceiver = randomAddress();
await host
.executeTransform(
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
refundReceiver,
}),
)
.awaitTransactionSuccessAsync({ value: protocolFee });
const receiverBalancer = await env.web3Wrapper.getBalanceInWeiAsync(refundReceiver);
expect(receiverBalancer).to.bignumber.eq(1);
});
it('can refund unspent protocol fee to the taker', async () => {
const orders = _.times(2, () => createOrder());
const signatures = orders.map(() => encodeExchangeBehavior());
const qfr = getExpectedSellQuoteFillResults(orders);
const protocolFee = qfr.protocolFeePaid.plus(1);
const refundReceiver = randomAddress();
await host
.executeTransform(
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
refundReceiver, // taker = refundReceiver
encodeTransformData({
orders,
signatures,
// address(1) indicates taker
refundReceiver: hexUtils.leftPad(1, 20),
}),
)
.awaitTransactionSuccessAsync({ value: protocolFee });
const receiverBalancer = await env.web3Wrapper.getBalanceInWeiAsync(refundReceiver);
expect(receiverBalancer).to.bignumber.eq(1);
});
it('can refund unspent protocol fee to the sender', async () => {
const orders = _.times(2, () => createOrder());
const signatures = orders.map(() => encodeExchangeBehavior());
const qfr = getExpectedSellQuoteFillResults(orders);
const protocolFee = qfr.protocolFeePaid.plus(1);
const refundReceiver = randomAddress();
await host
.executeTransform(
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
refundReceiver, // sender = refundReceiver
taker,
encodeTransformData({
orders,
signatures,
// address(2) indicates sender
refundReceiver: hexUtils.leftPad(2, 20),
}),
)
.awaitTransactionSuccessAsync({ value: protocolFee });
const receiverBalancer = await env.web3Wrapper.getBalanceInWeiAsync(refundReceiver);
expect(receiverBalancer).to.bignumber.eq(1);
});
});
describe('buy quotes', () => {
@ -652,6 +759,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -675,6 +784,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -701,6 +812,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -725,6 +838,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -751,6 +866,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -774,6 +891,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -804,6 +923,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -828,6 +949,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -852,6 +975,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -873,6 +998,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -900,6 +1027,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -926,6 +1055,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -952,6 +1083,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,
@ -980,6 +1113,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
transformer.address,
takerToken.address,
qfr.takerAssetSpent,
sender,
taker,
encodeTransformData({
orders,
signatures,

View File

@ -78,7 +78,12 @@ blockchainTests.resets('PayTakerTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
taker,
sender: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
expect(await getBalancesAsync(taker)).to.deep.eq({
@ -96,7 +101,12 @@ blockchainTests.resets('PayTakerTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
taker,
sender: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
expect(await getBalancesAsync(taker)).to.deep.eq({
@ -114,7 +124,12 @@ blockchainTests.resets('PayTakerTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
taker,
sender: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
expect(await getBalancesAsync(taker)).to.deep.eq({
@ -132,7 +147,12 @@ blockchainTests.resets('PayTakerTransformer', env => {
await mintHostTokensAsync(amounts[0]);
await sendEtherAsync(host.address, amounts[1]);
await host
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
.rawExecuteTransform(transformer.address, {
data,
callDataHash: hexUtils.random(),
taker,
sender: randomAddress(),
})
.awaitTransactionSuccessAsync();
expect(await getBalancesAsync(host.address)).to.deep.eq({
tokenBalance: amounts[0].minus(amounts[0].dividedToIntegerBy(2)),

View File

@ -5,15 +5,16 @@
*/
export * from '../test/generated-wrappers/affiliate_fee_transformer';
export * from '../test/generated-wrappers/allowance_target';
export * from '../test/generated-wrappers/bootstrap';
export * from '../test/generated-wrappers/bootstrap_feature';
export * from '../test/generated-wrappers/bridge_adapter';
export * from '../test/generated-wrappers/fill_quote_transformer';
export * from '../test/generated-wrappers/fixin_common';
export * from '../test/generated-wrappers/fixin_e_i_p712';
export * from '../test/generated-wrappers/fixin_reentrancy_guard';
export * from '../test/generated-wrappers/flash_wallet';
export * from '../test/generated-wrappers/full_migration';
export * from '../test/generated-wrappers/i_allowance_target';
export * from '../test/generated-wrappers/i_bootstrap';
export * from '../test/generated-wrappers/i_bootstrap_feature';
export * from '../test/generated-wrappers/i_bridge_adapter';
export * from '../test/generated-wrappers/i_erc20_bridge';
export * from '../test/generated-wrappers/i_erc20_transformer';
@ -21,13 +22,13 @@ export * from '../test/generated-wrappers/i_exchange';
export * from '../test/generated-wrappers/i_feature';
export * from '../test/generated-wrappers/i_flash_wallet';
export * from '../test/generated-wrappers/i_gas_token';
export * from '../test/generated-wrappers/i_meta_transactions';
export * from '../test/generated-wrappers/i_ownable';
export * from '../test/generated-wrappers/i_signature_validator';
export * from '../test/generated-wrappers/i_simple_function_registry';
export * from '../test/generated-wrappers/i_meta_transactions_feature';
export * from '../test/generated-wrappers/i_ownable_feature';
export * from '../test/generated-wrappers/i_signature_validator_feature';
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
export * from '../test/generated-wrappers/i_token_spender';
export * from '../test/generated-wrappers/i_transform_erc20';
export * from '../test/generated-wrappers/i_token_spender_feature';
export * from '../test/generated-wrappers/i_transform_erc20_feature';
export * from '../test/generated-wrappers/i_zero_ex';
export * from '../test/generated-wrappers/initial_migration';
export * from '../test/generated-wrappers/lib_bootstrap';
@ -40,6 +41,7 @@ export * from '../test/generated-wrappers/lib_ownable_rich_errors';
export * from '../test/generated-wrappers/lib_ownable_storage';
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
export * from '../test/generated-wrappers/lib_proxy_storage';
export * from '../test/generated-wrappers/lib_reentrancy_guard_storage';
export * from '../test/generated-wrappers/lib_signature_rich_errors';
export * from '../test/generated-wrappers/lib_signed_call_data';
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
@ -50,20 +52,22 @@ export * from '../test/generated-wrappers/lib_token_spender_storage';
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
export * from '../test/generated-wrappers/lib_wallet_rich_errors';
export * from '../test/generated-wrappers/meta_transactions';
export * from '../test/generated-wrappers/log_metadata_transformer';
export * from '../test/generated-wrappers/meta_transactions_feature';
export * from '../test/generated-wrappers/mixin_adapter_addresses';
export * from '../test/generated-wrappers/mixin_balancer';
export * from '../test/generated-wrappers/mixin_curve';
export * from '../test/generated-wrappers/mixin_kyber';
export * from '../test/generated-wrappers/mixin_m_stable';
export * from '../test/generated-wrappers/mixin_mooniswap';
export * from '../test/generated-wrappers/mixin_oasis';
export * from '../test/generated-wrappers/mixin_uniswap';
export * from '../test/generated-wrappers/mixin_uniswap_v2';
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
export * from '../test/generated-wrappers/ownable';
export * from '../test/generated-wrappers/ownable_feature';
export * from '../test/generated-wrappers/pay_taker_transformer';
export * from '../test/generated-wrappers/signature_validator';
export * from '../test/generated-wrappers/simple_function_registry';
export * from '../test/generated-wrappers/signature_validator_feature';
export * from '../test/generated-wrappers/simple_function_registry_feature';
export * from '../test/generated-wrappers/test_call_target';
export * from '../test/generated-wrappers/test_delegate_caller';
export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
@ -86,8 +90,8 @@ export * from '../test/generated-wrappers/test_transformer_host';
export * from '../test/generated-wrappers/test_weth';
export * from '../test/generated-wrappers/test_weth_transformer_host';
export * from '../test/generated-wrappers/test_zero_ex_feature';
export * from '../test/generated-wrappers/token_spender';
export * from '../test/generated-wrappers/transform_erc20';
export * from '../test/generated-wrappers/token_spender_feature';
export * from '../test/generated-wrappers/transform_erc20_feature';
export * from '../test/generated-wrappers/transformer';
export * from '../test/generated-wrappers/transformer_deployer';
export * from '../test/generated-wrappers/weth_transformer';

View File

@ -7,8 +7,8 @@ import { artifacts } from './artifacts';
import { initialMigrateAsync } from './utils/migration';
import {
IFeatureContract,
IOwnableContract,
ISimpleFunctionRegistryContract,
IOwnableFeatureContract,
ISimpleFunctionRegistryFeatureContract,
TestZeroExFeatureContract,
TestZeroExFeatureEvents,
} from './wrappers';
@ -16,15 +16,15 @@ import {
blockchainTests.resets('ZeroEx contract', env => {
let owner: string;
let zeroEx: ZeroExContract;
let ownable: IOwnableContract;
let registry: ISimpleFunctionRegistryContract;
let ownable: IOwnableFeatureContract;
let registry: ISimpleFunctionRegistryFeatureContract;
let testFeature: TestZeroExFeatureContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
zeroEx = await initialMigrateAsync(owner, env.provider, env.txDefaults);
ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults);
registry = new ISimpleFunctionRegistryContract(zeroEx.address, env.provider, env.txDefaults);
ownable = new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults);
registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults);
testFeature = new TestZeroExFeatureContract(zeroEx.address, env.provider, env.txDefaults);
// Register test features.
const testFeatureImpl = await TestZeroExFeatureContract.deployFrom0xArtifactAsync(

View File

@ -10,32 +10,34 @@
"generated-artifacts/IAllowanceTarget.json",
"generated-artifacts/IERC20Transformer.json",
"generated-artifacts/IFlashWallet.json",
"generated-artifacts/IOwnable.json",
"generated-artifacts/ISimpleFunctionRegistry.json",
"generated-artifacts/ITokenSpender.json",
"generated-artifacts/ITransformERC20.json",
"generated-artifacts/IOwnableFeature.json",
"generated-artifacts/ISimpleFunctionRegistryFeature.json",
"generated-artifacts/ITokenSpenderFeature.json",
"generated-artifacts/ITransformERC20Feature.json",
"generated-artifacts/IZeroEx.json",
"generated-artifacts/InitialMigration.json",
"generated-artifacts/MetaTransactions.json",
"generated-artifacts/Ownable.json",
"generated-artifacts/LogMetadataTransformer.json",
"generated-artifacts/MetaTransactionsFeature.json",
"generated-artifacts/OwnableFeature.json",
"generated-artifacts/PayTakerTransformer.json",
"generated-artifacts/SignatureValidator.json",
"generated-artifacts/SimpleFunctionRegistry.json",
"generated-artifacts/TokenSpender.json",
"generated-artifacts/TransformERC20.json",
"generated-artifacts/SignatureValidatorFeature.json",
"generated-artifacts/SimpleFunctionRegistryFeature.json",
"generated-artifacts/TokenSpenderFeature.json",
"generated-artifacts/TransformERC20Feature.json",
"generated-artifacts/WethTransformer.json",
"generated-artifacts/ZeroEx.json",
"test/generated-artifacts/AffiliateFeeTransformer.json",
"test/generated-artifacts/AllowanceTarget.json",
"test/generated-artifacts/Bootstrap.json",
"test/generated-artifacts/BootstrapFeature.json",
"test/generated-artifacts/BridgeAdapter.json",
"test/generated-artifacts/FillQuoteTransformer.json",
"test/generated-artifacts/FixinCommon.json",
"test/generated-artifacts/FixinEIP712.json",
"test/generated-artifacts/FixinReentrancyGuard.json",
"test/generated-artifacts/FlashWallet.json",
"test/generated-artifacts/FullMigration.json",
"test/generated-artifacts/IAllowanceTarget.json",
"test/generated-artifacts/IBootstrap.json",
"test/generated-artifacts/IBootstrapFeature.json",
"test/generated-artifacts/IBridgeAdapter.json",
"test/generated-artifacts/IERC20Bridge.json",
"test/generated-artifacts/IERC20Transformer.json",
@ -43,13 +45,13 @@
"test/generated-artifacts/IFeature.json",
"test/generated-artifacts/IFlashWallet.json",
"test/generated-artifacts/IGasToken.json",
"test/generated-artifacts/IMetaTransactions.json",
"test/generated-artifacts/IOwnable.json",
"test/generated-artifacts/ISignatureValidator.json",
"test/generated-artifacts/ISimpleFunctionRegistry.json",
"test/generated-artifacts/IMetaTransactionsFeature.json",
"test/generated-artifacts/IOwnableFeature.json",
"test/generated-artifacts/ISignatureValidatorFeature.json",
"test/generated-artifacts/ISimpleFunctionRegistryFeature.json",
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
"test/generated-artifacts/ITokenSpender.json",
"test/generated-artifacts/ITransformERC20.json",
"test/generated-artifacts/ITokenSpenderFeature.json",
"test/generated-artifacts/ITransformERC20Feature.json",
"test/generated-artifacts/IZeroEx.json",
"test/generated-artifacts/InitialMigration.json",
"test/generated-artifacts/LibBootstrap.json",
@ -62,6 +64,7 @@
"test/generated-artifacts/LibOwnableStorage.json",
"test/generated-artifacts/LibProxyRichErrors.json",
"test/generated-artifacts/LibProxyStorage.json",
"test/generated-artifacts/LibReentrancyGuardStorage.json",
"test/generated-artifacts/LibSignatureRichErrors.json",
"test/generated-artifacts/LibSignedCallData.json",
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
@ -72,20 +75,22 @@
"test/generated-artifacts/LibTransformERC20RichErrors.json",
"test/generated-artifacts/LibTransformERC20Storage.json",
"test/generated-artifacts/LibWalletRichErrors.json",
"test/generated-artifacts/MetaTransactions.json",
"test/generated-artifacts/LogMetadataTransformer.json",
"test/generated-artifacts/MetaTransactionsFeature.json",
"test/generated-artifacts/MixinAdapterAddresses.json",
"test/generated-artifacts/MixinBalancer.json",
"test/generated-artifacts/MixinCurve.json",
"test/generated-artifacts/MixinKyber.json",
"test/generated-artifacts/MixinMStable.json",
"test/generated-artifacts/MixinMooniswap.json",
"test/generated-artifacts/MixinOasis.json",
"test/generated-artifacts/MixinUniswap.json",
"test/generated-artifacts/MixinUniswapV2.json",
"test/generated-artifacts/MixinZeroExBridge.json",
"test/generated-artifacts/Ownable.json",
"test/generated-artifacts/OwnableFeature.json",
"test/generated-artifacts/PayTakerTransformer.json",
"test/generated-artifacts/SignatureValidator.json",
"test/generated-artifacts/SimpleFunctionRegistry.json",
"test/generated-artifacts/SignatureValidatorFeature.json",
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
"test/generated-artifacts/TestCallTarget.json",
"test/generated-artifacts/TestDelegateCaller.json",
"test/generated-artifacts/TestFillQuoteTransformerBridge.json",
@ -108,8 +113,8 @@
"test/generated-artifacts/TestWeth.json",
"test/generated-artifacts/TestWethTransformerHost.json",
"test/generated-artifacts/TestZeroExFeature.json",
"test/generated-artifacts/TokenSpender.json",
"test/generated-artifacts/TransformERC20.json",
"test/generated-artifacts/TokenSpenderFeature.json",
"test/generated-artifacts/TransformERC20Feature.json",
"test/generated-artifacts/Transformer.json",
"test/generated-artifacts/TransformerDeployer.json",
"test/generated-artifacts/WethTransformer.json",

View File

@ -85,6 +85,22 @@
{
"note": "Enable Quote Report to be generated with an option `shouldGenerateQuoteReport`. Default is `false`",
"pr": 2687
},
{
"note": "Add `refundReceiver` to `ExchangeProxySwapQuoteConsumer` options.",
"pr": 2657
},
{
"note": "Use `IZeroExContract` in EP swap quote consumer.",
"pr": 2657
},
{
"note": "Set `rfqtTakerAddress` to null in EP consumer",
"pr": 2692
},
{
"note": "Return Mooniswap pool in sampler and encode it in bridge data",
"pr": 2692
}
]
},

View File

@ -46,50 +46,69 @@ contract MooniswapSampler is
)
public
view
returns (uint256[] memory makerTokenAmounts)
returns (IMooniswap pool, uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
address _takerToken = takerToken == _getWethAddress() ? address(0) : takerToken;
address _makerToken = makerToken == _getWethAddress() ? address(0) : makerToken;
// Find the pool for the pair, ETH is represented
// as address(0)
IMooniswap pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(_takerToken, _makerToken)
);
// If there is no pool then return early
if (address(pool) == address(0)) {
return makerTokenAmounts;
}
uint256 poolBalance = _takerToken == address(0) ? address(pool).balance : IERC20Token(_takerToken).balanceOf(address(pool));
address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken;
address mooniswapMakerToken = makerToken == _getWethAddress() ? address(0) : makerToken;
for (uint256 i = 0; i < numSamples; i++) {
// If the pool balance is smaller than the sell amount
// don't sample to avoid multiplication overflow in buys
if (poolBalance < takerTokenAmounts[i]) {
break;
}
(bool didSucceed, bytes memory resultData) =
address(pool).staticcall.gas(MOONISWAP_CALL_GAS)(
abi.encodeWithSelector(
IMooniswap(0).getReturn.selector,
_takerToken,
_makerToken,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
}
uint256 buyAmount = sampleSingleSellFromMooniswapPool(
mooniswapTakerToken,
mooniswapMakerToken,
takerTokenAmounts[i]
);
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken)
);
}
function sampleSingleSellFromMooniswapPool(
address mooniswapTakerToken,
address mooniswapMakerToken,
uint256 takerTokenAmount
)
public
view
returns (uint256 makerTokenAmount)
{
// Find the pool for the pair.
IMooniswap pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken)
);
// If there is no pool then return early
if (address(pool) == address(0)) {
return makerTokenAmount;
}
uint256 poolBalance = mooniswapTakerToken == address(0)
? address(pool).balance
: IERC20Token(mooniswapTakerToken).balanceOf(address(pool));
// If the pool balance is smaller than the sell amount
// don't sample to avoid multiplication overflow in buys
if (poolBalance < takerTokenAmount) {
return makerTokenAmount;
}
(bool didSucceed, bytes memory resultData) =
address(pool).staticcall.gas(MOONISWAP_CALL_GAS)(
abi.encodeWithSelector(
pool.getReturn.selector,
mooniswapTakerToken,
mooniswapMakerToken,
takerTokenAmount
));
if (didSucceed) {
makerTokenAmount = abi.decode(resultData, (uint256));
}
}
/// @dev Sample buy quotes from Mooniswap.
@ -105,16 +124,27 @@ contract MooniswapSampler is
)
public
view
returns (uint256[] memory takerTokenAmounts)
returns (IMooniswap pool, uint256[] memory takerTokenAmounts)
{
return _sampleApproximateBuys(
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken;
address mooniswapMakerToken = makerToken == _getWethAddress() ? address(0) : makerToken;
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken),
takerTokenData: abi.encode(takerToken),
makerTokenData: abi.encode(mooniswapMakerToken),
takerTokenData: abi.encode(mooniswapTakerToken),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
}),
makerTokenAmounts
);
pool = IMooniswap(
IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken)
);
}
function _sampleSellForApproximateBuyFromMooniswap(
@ -126,21 +156,12 @@ contract MooniswapSampler is
view
returns (uint256 buyAmount)
{
(address takerToken) =
abi.decode(takerTokenData, (address));
(address makerToken) =
abi.decode(makerTokenData, (address));
(bool success, bytes memory resultData) =
address(this).staticcall(abi.encodeWithSelector(
this.sampleSellsFromMooniswap.selector,
takerToken,
makerToken,
_toSingleValueArray(sellAmount)
));
if (!success) {
return 0;
}
// solhint-disable-next-line indent
return abi.decode(resultData, (uint256[]))[0];
address mooniswapTakerToken = abi.decode(takerTokenData, (address));
address mooniswapMakerToken = abi.decode(makerTokenData, (address));
return sampleSingleSellFromMooniswapPool(
mooniswapTakerToken,
mooniswapMakerToken,
sellAmount
);
}
}

View File

@ -68,6 +68,7 @@ const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts
buyTokenFeeAmount: ZERO_AMOUNT,
sellTokenFeeAmount: ZERO_AMOUNT,
},
refundReceiver: NULL_ADDRESS,
};
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;

View File

@ -89,6 +89,7 @@ export {
AffiliateFee,
CalldataInfo,
ExchangeProxyContractOpts,
ExchangeProxyRefundReceiver,
ExtensionContractType,
ForwarderExtensionContractOpts,
GetExtensionContractTypeOpts,
@ -139,6 +140,7 @@ export {
LiquidityProviderFillData,
MarketDepth,
MarketDepthSide,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
NativeCollapsedFill,

View File

@ -1,5 +1,5 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { ITransformERC20Contract } from '@0x/contract-wrappers';
import { IZeroExContract } from '@0x/contract-wrappers';
import {
encodeAffiliateFeeTransformerData,
encodeFillQuoteTransformerData,
@ -7,10 +7,10 @@ import {
encodeWethTransformerData,
ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide,
findTransformerNonce,
} from '@0x/order-utils';
import { BigNumber, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import * as ethjs from 'ethereumjs-util';
import * as _ from 'lodash';
import { constants } from '../constants';
@ -32,7 +32,6 @@ import { getTokenFromAssetData } from '../utils/utils';
// tslint:disable-next-line:custom-no-magic-numbers
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
const { NULL_ADDRESS } = constants;
const MAX_NONCE_GUESSES = 2048;
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider;
@ -44,7 +43,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
affiliateFeeTransformer: number;
};
private readonly _transformFeature: ITransformERC20Contract;
private readonly _exchangeProxy: IZeroExContract;
constructor(
supportedProvider: SupportedProvider,
@ -57,7 +56,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
this.provider = provider;
this.chainId = chainId;
this.contractAddresses = contractAddresses;
this._transformFeature = new ITransformERC20Contract(contractAddresses.exchangeProxy, supportedProvider);
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, supportedProvider);
this.transformerNonces = {
wethTransformer: findTransformerNonce(
contractAddresses.transformers.wethTransformer,
@ -84,7 +83,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
): Promise<CalldataInfo> {
assert.isValidSwapQuote('quote', quote);
// tslint:disable-next-line:no-object-literal-type-assertion
const { affiliateFee, isFromETH, isToETH } = {
const { refundReceiver, affiliateFee, isFromETH, isToETH } = {
...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
...opts.extensionContractOpts,
} as ExchangeProxyContractOpts;
@ -116,8 +115,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
sellToken,
buyToken: intermediateToken,
side: FillQuoteTransformerSide.Sell,
refundReceiver: refundReceiver || NULL_ADDRESS,
fillAmount: firstHopOrder.takerAssetAmount,
maxOrderFillAmounts: [],
rfqtTakerAddress: NULL_ADDRESS,
orders: [firstHopOrder],
signatures: [firstHopOrder.signature],
}),
@ -125,11 +126,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
transforms.push({
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
data: encodeFillQuoteTransformerData({
sellToken: intermediateToken,
buyToken,
sellToken: intermediateToken,
refundReceiver: refundReceiver || NULL_ADDRESS,
side: FillQuoteTransformerSide.Sell,
fillAmount: MAX_UINT256,
maxOrderFillAmounts: [],
rfqtTakerAddress: NULL_ADDRESS,
orders: [secondHopOrder],
signatures: [secondHopOrder.signature],
}),
@ -140,9 +143,11 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
data: encodeFillQuoteTransformerData({
sellToken,
buyToken,
refundReceiver: refundReceiver || NULL_ADDRESS,
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
fillAmount: isBuyQuote(quote) ? quote.makerAssetFillAmount : quote.takerAssetFillAmount,
maxOrderFillAmounts: [],
rfqtTakerAddress: NULL_ADDRESS,
orders: quote.orders,
signatures: quote.orders.map(o => o.signature),
}),
@ -192,7 +197,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
});
const minBuyAmount = BigNumber.max(0, quote.worstCaseQuoteInfo.makerAssetAmount.minus(buyTokenFeeAmount));
const calldataHexString = this._transformFeature
const calldataHexString = this._exchangeProxy
.transformERC20(
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
@ -210,7 +215,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
return {
calldataHexString,
ethAmount,
toAddress: this._transformFeature.address,
toAddress: this._exchangeProxy.address,
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
};
}
@ -227,32 +232,3 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
/**
* Find the nonce for a transformer given its deployer.
* If `deployer` is the null address, zero will always be returned.
*/
export function findTransformerNonce(transformer: string, deployer: string = NULL_ADDRESS): number {
if (deployer === NULL_ADDRESS) {
return 0;
}
const lowercaseTransformer = transformer.toLowerCase();
// Try to guess the nonce.
for (let nonce = 0; nonce < MAX_NONCE_GUESSES; ++nonce) {
const deployedAddress = getTransformerAddress(deployer, nonce);
if (deployedAddress === lowercaseTransformer) {
return nonce;
}
}
throw new Error(`${deployer} did not deploy ${transformer}!`);
}
/**
* Compute the deployed address for a transformer given a deployer and nonce.
*/
export function getTransformerAddress(deployer: string, nonce: number): string {
return ethjs.bufferToHex(
// tslint:disable-next-line: custom-no-magic-numbers
ethjs.rlphash([deployer, nonce] as any).slice(12),
);
}

View File

@ -135,15 +135,31 @@ export interface AffiliateFee {
sellTokenFeeAmount: BigNumber;
}
/**
* Automatically resolved protocol fee refund receiver addresses.
*/
export enum ExchangeProxyRefundReceiver {
// Refund to the taker address.
Taker = '0x0000000000000000000000000000000000000001',
// Refund to the sender address.
Sender = '0x0000000000000000000000000000000000000002',
}
/**
* @param isFromETH Whether the input token is ETH.
* @param isToETH Whether the output token is ETH.
* @param affiliateFee Fee denominated in taker or maker asset to send to specified recipient.
* @param refundReceiver The receiver of unspent protocol fees.
* May be a valid address or one of:
* `address(0)`: Stay in flash wallet.
* `address(1)`: Send to the taker.
* `address(2)`: Send to the sender (caller of `transformERC20()`).
*/
export interface ExchangeProxyContractOpts {
isFromETH: boolean;
isToETH: boolean;
affiliateFee: AffiliateFee;
refundReceiver: string | ExchangeProxyRefundReceiver;
}
export interface GetExtensionContractTypeOpts {

View File

@ -29,6 +29,7 @@ import {
Fill,
KyberFillData,
LiquidityProviderFillData,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
NativeCollapsedFill,
@ -304,6 +305,14 @@ function createBridgeOrder(
createKyberBridgeData(takerToken, kyberFillData.hint),
);
break;
case ERC20BridgeSource.Mooniswap:
const mooniswapFillData = (fill as CollapsedFill<MooniswapFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createMooniswapBridgeData(takerToken, mooniswapFillData.poolAddress),
);
break;
default:
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
@ -407,6 +416,14 @@ function createKyberBridgeData(fromTokenAddress: string, hint: string): string {
return encoder.encode({ fromTokenAddress, hint });
}
function createMooniswapBridgeData(takerToken: string, poolAddress: string): string {
const encoder = AbiEncoder.create([
{ name: 'takerToken', type: 'address' },
{ name: 'poolAddress', type: 'address' },
]);
return encoder.encode({ takerToken, poolAddress });
}
function createCurveBridgeData(
curveAddress: string,
exchangeFunctionSelector: string,

View File

@ -23,6 +23,7 @@ import {
HopInfo,
KyberFillData,
LiquidityProviderFillData,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
SourceQuoteOperation,
@ -446,6 +447,14 @@ export class SamplerOperations {
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromMooniswap,
params: [takerToken, makerToken, takerFillAmounts],
callback: (callResults: string, fillData: MooniswapFillData): BigNumber[] => {
const [poolAddress, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>(
'sampleSellsFromMooniswap',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
});
}
@ -459,6 +468,14 @@ export class SamplerOperations {
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromMooniswap,
params: [takerToken, makerToken, makerFillAmounts],
callback: (callResults: string, fillData: MooniswapFillData): BigNumber[] => {
const [poolAddress, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>(
'sampleBuysFromMooniswap',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
});
}

View File

@ -113,6 +113,10 @@ export interface KyberFillData extends FillData {
reserveId: string;
}
export interface MooniswapFillData extends FillData {
poolAddress: string;
}
export interface Quote<TFillData = FillData> {
amount: BigNumber;
fillData?: TFillData;

View File

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

View File

@ -24,8 +24,8 @@ describe('quote_simulation tests', async () => {
const DEFAULT_TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
const GAS_SCHEDULE = { [ERC20BridgeSource.Native]: _.constant(1) };
// Check if two numbers are within `maxError` error rate within each other (default 1 bps).
function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-12): void {
// Check if two numbers are within `maxError` error rate within each other.
function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-10): void {
// |n2-n1| / max(|n1|, |n2|)
const err = n2
.minus(n1)

Some files were not shown because too many files have changed in this diff Show More