diff --git a/contracts/exchange/test/dispatcher.ts b/contracts/exchange/test/dispatcher.ts index 7d36aca270..fdddc21e95 100644 --- a/contracts/exchange/test/dispatcher.ts +++ b/contracts/exchange/test/dispatcher.ts @@ -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( diff --git a/contracts/exchange/test/signature_validator.ts b/contracts/exchange/test/signature_validator.ts index 8dce19f20c..07d66760d2 100644 --- a/contracts/exchange/test/signature_validator.ts +++ b/contracts/exchange/test/signature_validator.ts @@ -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); diff --git a/contracts/exchange/test/transactions_unit_tests.ts b/contracts/exchange/test/transactions_unit_tests.ts index 318e9c472d..a237bf3853 100644 --- a/contracts/exchange/test/transactions_unit_tests.ts +++ b/contracts/exchange/test/transactions_unit_tests.ts @@ -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) diff --git a/contracts/integrations/CHANGELOG.json b/contracts/integrations/CHANGELOG.json index 50c5c3863e..e290a2aa00 100644 --- a/contracts/integrations/CHANGELOG.json +++ b/contracts/integrations/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Update curveBridge tests", "pr": 2633 + }, + { + "note": "Add EP RFQT + MTX tests", + "pr": 2692 } ] }, diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index b00f722aa8..1065d09871 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -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", diff --git a/contracts/integrations/test/exchange-proxy/mtx_test.ts b/contracts/integrations/test/exchange-proxy/mtx_test.ts new file mode 100644 index 0000000000..c1844e84b3 --- /dev/null +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -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 = {}, isRfqt: boolean = false): Promise { + 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 { + 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()); + }); +}); diff --git a/contracts/integrations/test/exchange/core_test.ts b/contracts/integrations/test/exchange/core_test.ts index 0dda0fde5c..5bdfa76ec7 100644 --- a/contracts/integrations/test/exchange/core_test.ts +++ b/contracts/integrations/test/exchange/core_test.ts @@ -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) diff --git a/contracts/integrations/test/exchange/fill_dydx_order_test.ts b/contracts/integrations/test/exchange/fill_dydx_order_test.ts index 6cfa83c34f..3c5bdc2216 100644 --- a/contracts/integrations/test/exchange/fill_dydx_order_test.ts +++ b/contracts/integrations/test/exchange/fill_dydx_order_test.ts @@ -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())); }); }); }); diff --git a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts index c4c1097252..09c794a73e 100644 --- a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts +++ b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts @@ -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); }); diff --git a/contracts/integrations/test/exchange/transaction_test.ts b/contracts/integrations/test/exchange/transaction_test.ts index 77266aea7a..749ea812e3 100644 --- a/contracts/integrations/test/exchange/transaction_test.ts +++ b/contracts/integrations/test/exchange/transaction_test.ts @@ -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, diff --git a/contracts/integrations/test/stop-limit/chainlink_stop_limit_test.ts b/contracts/integrations/test/stop-limit/chainlink_stop_limit_test.ts index a76a1e46e3..cb3928fddf 100644 --- a/contracts/integrations/test/stop-limit/chainlink_stop_limit_test.ts +++ b/contracts/integrations/test/stop-limit/chainlink_stop_limit_test.ts @@ -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); }); diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 69e75db6b6..65018dc553 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -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": [ diff --git a/contracts/zero-ex/contracts/src/IZeroEx.sol b/contracts/zero-ex/contracts/src/IZeroEx.sol index c1ccfb28e3..62a8f832f8 100644 --- a/contracts/zero-ex/contracts/src/IZeroEx.sol +++ b/contracts/zero-ex/contracts/src/IZeroEx.sol @@ -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 diff --git a/contracts/zero-ex/contracts/src/ZeroEx.sol b/contracts/zero-ex/contracts/src/ZeroEx.sol index 16a1b9fcd0..a18d762a93 100644 --- a/contracts/zero-ex/contracts/src/ZeroEx.sol +++ b/contracts/zero-ex/contracts/src/ZeroEx.sol @@ -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); } diff --git a/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol index 17a038c1d9..f5b6e5952f 100644 --- a/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol +++ b/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol @@ -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 ); } } diff --git a/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol b/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol index 3e3243605c..5ef07219b2 100644 --- a/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol +++ b/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol @@ -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); } } diff --git a/contracts/zero-ex/contracts/src/features/Bootstrap.sol b/contracts/zero-ex/contracts/src/features/BootstrapFeature.sol similarity index 94% rename from contracts/zero-ex/contracts/src/features/Bootstrap.sol rename to contracts/zero-ex/contracts/src/features/BootstrapFeature.sol index 3a65797818..aa4d484e3e 100644 --- a/contracts/zero-ex/contracts/src/features/Bootstrap.sol +++ b/contracts/zero-ex/contracts/src/features/BootstrapFeature.sol @@ -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(); } diff --git a/contracts/zero-ex/contracts/src/features/IBootstrap.sol b/contracts/zero-ex/contracts/src/features/IBootstrapFeature.sol similarity index 97% rename from contracts/zero-ex/contracts/src/features/IBootstrap.sol rename to contracts/zero-ex/contracts/src/features/IBootstrapFeature.sol index ba7e9724cc..f9a26116c5 100644 --- a/contracts/zero-ex/contracts/src/features/IBootstrap.sol +++ b/contracts/zero-ex/contracts/src/features/IBootstrapFeature.sol @@ -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 diff --git a/contracts/zero-ex/contracts/src/features/IMetaTransactions.sol b/contracts/zero-ex/contracts/src/features/IMetaTransactionsFeature.sol similarity index 99% rename from contracts/zero-ex/contracts/src/features/IMetaTransactions.sol rename to contracts/zero-ex/contracts/src/features/IMetaTransactionsFeature.sol index 0c9f4e4b9a..ceb14e2d36 100644 --- a/contracts/zero-ex/contracts/src/features/IMetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/IMetaTransactionsFeature.sol @@ -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 { diff --git a/contracts/zero-ex/contracts/src/features/IOwnable.sol b/contracts/zero-ex/contracts/src/features/IOwnableFeature.sol similarity index 98% rename from contracts/zero-ex/contracts/src/features/IOwnable.sol rename to contracts/zero-ex/contracts/src/features/IOwnableFeature.sol index a15dcf3625..f2cdc278af 100644 --- a/contracts/zero-ex/contracts/src/features/IOwnable.sol +++ b/contracts/zero-ex/contracts/src/features/IOwnableFeature.sol @@ -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. diff --git a/contracts/zero-ex/contracts/src/features/ISignatureValidator.sol b/contracts/zero-ex/contracts/src/features/ISignatureValidatorFeature.sol similarity index 98% rename from contracts/zero-ex/contracts/src/features/ISignatureValidator.sol rename to contracts/zero-ex/contracts/src/features/ISignatureValidatorFeature.sol index 121e27c033..528deb99b7 100644 --- a/contracts/zero-ex/contracts/src/features/ISignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/ISignatureValidatorFeature.sol @@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2; /// @dev Feature for validating signatures. -interface ISignatureValidator { +interface ISignatureValidatorFeature { /// @dev Allowed signature types. enum SignatureType { diff --git a/contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistry.sol b/contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistryFeature.sol similarity index 98% rename from contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistry.sol rename to contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistryFeature.sol index 6aabc17889..1e5ef61c25 100644 --- a/contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistry.sol +++ b/contracts/zero-ex/contracts/src/features/ISimpleFunctionRegistryFeature.sol @@ -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. diff --git a/contracts/zero-ex/contracts/src/features/ITokenSpender.sol b/contracts/zero-ex/contracts/src/features/ITokenSpenderFeature.sol similarity index 98% rename from contracts/zero-ex/contracts/src/features/ITokenSpender.sol rename to contracts/zero-ex/contracts/src/features/ITokenSpenderFeature.sol index 8e6128cd5e..0c3fc44f1d 100644 --- a/contracts/zero-ex/contracts/src/features/ITokenSpender.sol +++ b/contracts/zero-ex/contracts/src/features/ITokenSpenderFeature.sol @@ -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. diff --git a/contracts/zero-ex/contracts/src/features/ITransformERC20.sol b/contracts/zero-ex/contracts/src/features/ITransformERC20Feature.sol similarity index 99% rename from contracts/zero-ex/contracts/src/features/ITransformERC20.sol rename to contracts/zero-ex/contracts/src/features/ITransformERC20Feature.sol index b9a8894219..89a8f8e41f 100644 --- a/contracts/zero-ex/contracts/src/features/ITransformERC20.sol +++ b/contracts/zero-ex/contracts/src/features/ITransformERC20Feature.sol @@ -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 { diff --git a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol b/contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol similarity index 88% rename from contracts/zero-ex/contracts/src/features/MetaTransactions.sol rename to contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol index 975520a6a5..bd6e40b1c5 100644 --- a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol @@ -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, diff --git a/contracts/zero-ex/contracts/src/features/Ownable.sol b/contracts/zero-ex/contracts/src/features/OwnableFeature.sol similarity index 88% rename from contracts/zero-ex/contracts/src/features/Ownable.sol rename to contracts/zero-ex/contracts/src/features/OwnableFeature.sol index e6ad228178..29502eeb8c 100644 --- a/contracts/zero-ex/contracts/src/features/Ownable.sol +++ b/contracts/zero-ex/contracts/src/features/OwnableFeature.sol @@ -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; } diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidatorFeature.sol similarity index 87% rename from contracts/zero-ex/contracts/src/features/SignatureValidator.sol rename to contracts/zero-ex/contracts/src/features/SignatureValidatorFeature.sol index b7e6b1b71a..cea91baacc 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidatorFeature.sol @@ -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'); diff --git a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistryFeature.sol similarity index 96% rename from contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol rename to contracts/zero-ex/contracts/src/features/SimpleFunctionRegistryFeature.sol index f9da65102f..ac98b64dc7 100644 --- a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol +++ b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistryFeature.sol @@ -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() diff --git a/contracts/zero-ex/contracts/src/features/TokenSpender.sol b/contracts/zero-ex/contracts/src/features/TokenSpenderFeature.sol similarity index 95% rename from contracts/zero-ex/contracts/src/features/TokenSpender.sol rename to contracts/zero-ex/contracts/src/features/TokenSpenderFeature.sol index 509dd24b95..1b12a45d46 100644 --- a/contracts/zero-ex/contracts/src/features/TokenSpender.sol +++ b/contracts/zero-ex/contracts/src/features/TokenSpenderFeature.sol @@ -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 diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol similarity index 91% rename from contracts/zero-ex/contracts/src/features/TransformERC20.sol rename to contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol index e7bafaa355..fa77fba85a 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol @@ -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; diff --git a/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol b/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol index 46a0c0eaf0..37ef41d652 100644 --- a/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol +++ b/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol @@ -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`. diff --git a/contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol b/contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol index fac8231066..ea41cd47e1 100644 --- a/contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol +++ b/contracts/zero-ex/contracts/src/fixins/FixinEIP712.sol @@ -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. diff --git a/contracts/zero-ex/contracts/src/fixins/FixinReentrancyGuard.sol b/contracts/zero-ex/contracts/src/fixins/FixinReentrancyGuard.sol new file mode 100644 index 0000000000..f8081dc3d4 --- /dev/null +++ b/contracts/zero-ex/contracts/src/fixins/FixinReentrancyGuard.sol @@ -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); + } +} diff --git a/contracts/zero-ex/contracts/src/migrations/FullMigration.sol b/contracts/zero-ex/contracts/src/migrations/FullMigration.sol index 5cefc83643..5127be284d 100644 --- a/contracts/zero-ex/contracts/src/migrations/FullMigration.sol +++ b/contracts/zero-ex/contracts/src/migrations/FullMigration.sol @@ -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) ); diff --git a/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol b/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol index 8868a8a08c..7ccdfdb33f 100644 --- a/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol +++ b/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol @@ -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; } diff --git a/contracts/zero-ex/contracts/src/storage/LibReentrancyGuardStorage.sol b/contracts/zero-ex/contracts/src/storage/LibReentrancyGuardStorage.sol new file mode 100644 index 0000000000..58c52fb467 --- /dev/null +++ b/contracts/zero-ex/contracts/src/storage/LibReentrancyGuardStorage.sol @@ -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 } + } +} diff --git a/contracts/zero-ex/contracts/src/storage/LibStorage.sol b/contracts/zero-ex/contracts/src/storage/LibStorage.sol index 1af79e919f..809977d4a8 100644 --- a/contracts/zero-ex/contracts/src/storage/LibStorage.sol +++ b/contracts/zero-ex/contracts/src/storage/LibStorage.sol @@ -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 diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 17025ef0b2..c2c44e6f39 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -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) { diff --git a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol index c2e8b10d15..78cd6103c4 100644 --- a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol @@ -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); diff --git a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol index 40fd9387c0..928c432745 100644 --- a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol @@ -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); } diff --git a/contracts/zero-ex/contracts/src/transformers/LogMetadataTransformer.sol b/contracts/zero-ex/contracts/src/transformers/LogMetadataTransformer.sol new file mode 100644 index 0000000000..13869e6c15 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/LogMetadataTransformer.sol @@ -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; + } +} diff --git a/contracts/zero-ex/contracts/src/transformers/PayTakerTransformer.sol b/contracts/zero-ex/contracts/src/transformers/PayTakerTransformer.sol index 4b03759ab1..37925301a0 100644 --- a/contracts/zero-ex/contracts/src/transformers/PayTakerTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/PayTakerTransformer.sol @@ -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; diff --git a/contracts/zero-ex/contracts/src/transformers/WethTransformer.sol b/contracts/zero-ex/contracts/src/transformers/WethTransformer.sol index 503712cfd9..3fa744f504 100644 --- a/contracts/zero-ex/contracts/src/transformers/WethTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/WethTransformer.sol @@ -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(); } diff --git a/contracts/zero-ex/contracts/src/bridges/BridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol similarity index 82% rename from contracts/zero-ex/contracts/src/bridges/BridgeAdapter.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol index 412403e0ef..bd90f82928 100644 --- a/contracts/zero-ex/contracts/src/bridges/BridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol @@ -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; } } diff --git a/contracts/zero-ex/contracts/src/bridges/IBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/IBridgeAdapter.sol similarity index 100% rename from contracts/zero-ex/contracts/src/bridges/IBridgeAdapter.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/IBridgeAdapter.sol diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinAdapterAddresses.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol similarity index 97% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinAdapterAddresses.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol index 46d84d876d..7a08ad2973 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinAdapterAddresses.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol @@ -26,6 +26,7 @@ contract MixinAdapterAddresses address balancerBridge; address curveBridge; address kyberBridge; + address mooniswapBridge; address mStableBridge; address oasisBridge; address uniswapBridge; diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinBalancer.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancer.sol similarity index 74% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinBalancer.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancer.sol index 3efb11741d..a9fbf9708b 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinBalancer.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancer.sol @@ -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; } diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinCurve.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinCurve.sol similarity index 84% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinCurve.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinCurve.sol index e3707e06ad..dfd8c926d2 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinCurve.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinCurve.sol @@ -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); } } diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinKyber.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyber.sol similarity index 76% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinKyber.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyber.sol index fa37a5150a..6deb2a90d2 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinKyber.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinKyber.sol @@ -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; diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinMStable.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMStable.sol similarity index 73% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinMStable.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMStable.sol index 4b960831bb..c5edfc7ab6 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinMStable.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMStable.sol @@ -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) ); diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol new file mode 100644 index 0000000000..4cbe634eae --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol @@ -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}(); + } + } +} diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinOasis.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinOasis.sol similarity index 63% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinOasis.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinOasis.sol index cf1f21fe2e..abb6f78540 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinOasis.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinOasis.sol @@ -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 ); diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswap.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswap.sol similarity index 79% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswap.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswap.sol index 5fff0d88b6..bf79e4c354 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswap.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswap.sol @@ -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; } } diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswapV2.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV2.sol similarity index 90% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswapV2.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV2.sol index 612a790150..bea390acef 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinUniswapV2.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV2.sol @@ -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), diff --git a/contracts/zero-ex/contracts/src/bridges/mixins/MixinZeroExBridge.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinZeroExBridge.sol similarity index 77% rename from contracts/zero-ex/contracts/src/bridges/mixins/MixinZeroExBridge.sol rename to contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinZeroExBridge.sol index 7bd3fb9aca..234d293adc 100644 --- a/contracts/zero-ex/contracts/src/bridges/mixins/MixinZeroExBridge.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinZeroExBridge.sol @@ -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); } } diff --git a/contracts/zero-ex/contracts/test/TestFillQuoteTransformerHost.sol b/contracts/zero-ex/contracts/test/TestFillQuoteTransformerHost.sol index 6b85acd2f5..250a8dd9da 100644 --- a/contracts/zero-ex/contracts/test/TestFillQuoteTransformerHost.sol +++ b/contracts/zero-ex/contracts/test/TestFillQuoteTransformerHost.sol @@ -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 + }) + ); } } diff --git a/contracts/zero-ex/contracts/test/TestFullMigration.sol b/contracts/zero-ex/contracts/test/TestFullMigration.sol index 6777af099c..61244812e0 100644 --- a/contracts/zero-ex/contracts/test/TestFullMigration.sol +++ b/contracts/zero-ex/contracts/test/TestFullMigration.sol @@ -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"; diff --git a/contracts/zero-ex/contracts/test/TestInitialMigration.sol b/contracts/zero-ex/contracts/test/TestInitialMigration.sol index 2f57d731f5..f6337bc5ed 100644 --- a/contracts/zero-ex/contracts/test/TestInitialMigration.sol +++ b/contracts/zero-ex/contracts/test/TestInitialMigration.sol @@ -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 { diff --git a/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol index 913fa414d7..bb31d76187 100644 --- a/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol @@ -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, diff --git a/contracts/zero-ex/contracts/test/TestMigrator.sol b/contracts/zero-ex/contracts/test/TestMigrator.sol index f81a589fd3..db9d68bb84 100644 --- a/contracts/zero-ex/contracts/test/TestMigrator.sol +++ b/contracts/zero-ex/contracts/test/TestMigrator.sol @@ -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; } diff --git a/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol b/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol index 2bd2463b64..bb5fccd464 100644 --- a/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol +++ b/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol @@ -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; } diff --git a/contracts/zero-ex/contracts/test/TestTokenSpender.sol b/contracts/zero-ex/contracts/test/TestTokenSpender.sol index 8213b0d503..8c64a6503c 100644 --- a/contracts/zero-ex/contracts/test/TestTokenSpender.sol +++ b/contracts/zero-ex/contracts/test/TestTokenSpender.sol @@ -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 { _; diff --git a/contracts/zero-ex/contracts/test/TestTransformERC20.sol b/contracts/zero-ex/contracts/test/TestTransformERC20.sol index 0e133d81d1..6afc5f0dbe 100644 --- a/contracts/zero-ex/contracts/test/TestTransformERC20.sol +++ b/contracts/zero-ex/contracts/test/TestTransformERC20.sol @@ -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 { _; } diff --git a/contracts/zero-ex/contracts/test/TestTransformerBase.sol b/contracts/zero-ex/contracts/test/TestTransformerBase.sol index d08151d3b4..ba6415310b 100644 --- a/contracts/zero-ex/contracts/test/TestTransformerBase.sol +++ b/contracts/zero-ex/contracts/test/TestTransformerBase.sol @@ -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) diff --git a/contracts/zero-ex/contracts/test/TestTransformerDeployerTransformer.sol b/contracts/zero-ex/contracts/test/TestTransformerDeployerTransformer.sol index f234d33c03..2491920718 100644 --- a/contracts/zero-ex/contracts/test/TestTransformerDeployerTransformer.sol +++ b/contracts/zero-ex/contracts/test/TestTransformerDeployerTransformer.sol @@ -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) diff --git a/contracts/zero-ex/contracts/test/TestTransformerHost.sol b/contracts/zero-ex/contracts/test/TestTransformerHost.sol index 587b837de9..458a12b824 100644 --- a/contracts/zero-ex/contracts/test/TestTransformerHost.sol +++ b/contracts/zero-ex/contracts/test/TestTransformerHost.sol @@ -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(); diff --git a/contracts/zero-ex/contracts/test/TestWethTransformerHost.sol b/contracts/zero-ex/contracts/test/TestWethTransformerHost.sol index 3c0fd83999..6e217c3257 100644 --- a/contracts/zero-ex/contracts/test/TestWethTransformerHost.sol +++ b/contracts/zero-ex/contracts/test/TestWethTransformerHost.sol @@ -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 + }) + ); } } diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 251ebde18e..528789cde1 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -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", diff --git a/contracts/zero-ex/src/artifacts.ts b/contracts/zero-ex/src/artifacts.ts index fc0e25c8a8..b51add69e6 100644 --- a/contracts/zero-ex/src/artifacts.ts +++ b/contracts/zero-ex/src/artifacts.ts @@ -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, }; diff --git a/contracts/zero-ex/src/index.ts b/contracts/zero-ex/src/index.ts index ee15391e57..6ad89649d3 100644 --- a/contracts/zero-ex/src/index.ts +++ b/contracts/zero-ex/src/index.ts @@ -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, diff --git a/contracts/zero-ex/src/migration.ts b/contracts/zero-ex/src/migration.ts index 8d211800e0..6277b8931d 100644 --- a/contracts/zero-ex/src/migration.ts +++ b/contracts/zero-ex/src/migration.ts @@ -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, features: Partial = {}, opts: Partial = {}, -): Promise { +): Promise { 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); } diff --git a/contracts/zero-ex/src/wrappers.ts b/contracts/zero-ex/src/wrappers.ts index ef626d0422..ea09d3416d 100644 --- a/contracts/zero-ex/src/wrappers.ts +++ b/contracts/zero-ex/src/wrappers.ts @@ -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'; diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 70a5b4b55f..b0e690901d 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -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, diff --git a/contracts/zero-ex/test/features/meta_transactions_test.ts b/contracts/zero-ex/test/features/meta_transactions_test.ts index 943da28a5a..8e08b02d45 100644 --- a/contracts/zero-ex/test/features/meta_transactions_test.ts +++ b/contracts/zero-ex/test/features/meta_transactions_test.ts @@ -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()', () => { diff --git a/contracts/zero-ex/test/features/ownable_test.ts b/contracts/zero-ex/test/features/ownable_test.ts index 4c4f4d1708..8f3deb5c81 100644 --- a/contracts/zero-ex/test/features/ownable_test.ts +++ b/contracts/zero-ex/test/features/ownable_test.ts @@ -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(), ), ); }); diff --git a/contracts/zero-ex/test/features/signature_validator_test.ts b/contracts/zero-ex/test/features/signature_validator_test.ts index 99fdd79e9b..2ea0b7e636 100644 --- a/contracts/zero-ex/test/features/signature_validator_test.ts +++ b/contracts/zero-ex/test/features/signature_validator_test.ts @@ -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()', () => { diff --git a/contracts/zero-ex/test/features/simple_function_registry_test.ts b/contracts/zero-ex/test/features/simple_function_registry_test.ts index 4f4f06fd5f..53d0ec99b0 100644 --- a/contracts/zero-ex/test/features/simple_function_registry_test.ts +++ b/contracts/zero-ex/test/features/simple_function_registry_test.ts @@ -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); diff --git a/contracts/zero-ex/test/features/token_spender_test.ts b/contracts/zero-ex/test/features/token_spender_test.ts index 825ed49478..0a1d24b8b1 100644 --- a/contracts/zero-ex/test/features/token_spender_test.ts +++ b/contracts/zero-ex/test/features/token_spender_test.ts @@ -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); }); diff --git a/contracts/zero-ex/test/features/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 4625325a9c..77e44a3a6b 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -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; @@ -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'); diff --git a/contracts/zero-ex/test/flash_wallet_test.ts b/contracts/zero-ex/test/flash_wallet_test.ts index 51079aeaee..f13f0fa6a2 100644 --- a/contracts/zero-ex/test/flash_wallet_test.ts +++ b/contracts/zero-ex/test/flash_wallet_test.ts @@ -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'), ), ); }); diff --git a/contracts/zero-ex/test/full_migration_test.ts b/contracts/zero-ex/test/full_migration_test.ts index dea5bf3749..0611e45420 100644 --- a/contracts/zero-ex/test/full_migration_test.ts +++ b/contracts/zero-ex/test/full_migration_test.ts @@ -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 () => { diff --git a/contracts/zero-ex/test/initial_migration_test.ts b/contracts/zero-ex/test/initial_migration_test.ts index f9985ea4c2..6fafe719ae 100644 --- a/contracts/zero-ex/test/initial_migration_test.ts +++ b/contracts/zero-ex/test/initial_migration_test.ts @@ -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 () => { diff --git a/contracts/zero-ex/test/transformer_deployer_test.ts b/contracts/zero-ex/test/transformer_deployer_test.ts index c3d7e15658..2d6f614102 100644 --- a/contracts/zero-ex/test/transformer_deployer_test.ts +++ b/contracts/zero-ex/test/transformer_deployer_test.ts @@ -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 }], diff --git a/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts b/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts index 1fac52fe99..6cd2b94cbe 100644 --- a/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts @@ -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), diff --git a/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts index 81dc82841f..3ad6c5a6d4 100644 --- a/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts @@ -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, diff --git a/contracts/zero-ex/test/transformers/pay_taker_transformer_test.ts b/contracts/zero-ex/test/transformers/pay_taker_transformer_test.ts index f7570e7c62..4b1add082c 100644 --- a/contracts/zero-ex/test/transformers/pay_taker_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/pay_taker_transformer_test.ts @@ -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)), diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 9496db026f..7b775aa0f6 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -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'; diff --git a/contracts/zero-ex/test/zero_ex_test.ts b/contracts/zero-ex/test/zero_ex_test.ts index ade1ab2b94..566504f157 100644 --- a/contracts/zero-ex/test/zero_ex_test.ts +++ b/contracts/zero-ex/test/zero_ex_test.ts @@ -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( diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 0acd2f5537..9d53f0d9c0 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -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", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index e69ee2d1dc..d09eb21524 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -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 } ] }, diff --git a/packages/asset-swapper/contracts/src/MooniswapSampler.sol b/packages/asset-swapper/contracts/src/MooniswapSampler.sol index 2e2dfc2edb..0c19e7d72b 100644 --- a/packages/asset-swapper/contracts/src/MooniswapSampler.sol +++ b/packages/asset-swapper/contracts/src/MooniswapSampler.sol @@ -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 + ); } } diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index 54a0b0d4a0..d085ff64a6 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -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; diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 64d4b32e4e..fbf49b2185 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -89,6 +89,7 @@ export { AffiliateFee, CalldataInfo, ExchangeProxyContractOpts, + ExchangeProxyRefundReceiver, ExtensionContractType, ForwarderExtensionContractOpts, GetExtensionContractTypeOpts, @@ -139,6 +140,7 @@ export { LiquidityProviderFillData, MarketDepth, MarketDepthSide, + MooniswapFillData, MultiBridgeFillData, MultiHopFillData, NativeCollapsedFill, diff --git a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts index b77dffac6e..a7ce975feb 100644 --- a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts @@ -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 { 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), - ); -} diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index 783e9e725a..e7d9c8bf6f 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -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 { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 828c69016b..76f3ee01f5 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -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).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, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index e06e31a627..a67d3dce66 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -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; + }, }); } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 4269f87850..e8040c7a86 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -113,6 +113,10 @@ export interface KyberFillData extends FillData { reserveId: string; } +export interface MooniswapFillData extends FillData { + poolAddress: string; +} + export interface Quote { amount: BigNumber; fillData?: TFillData; diff --git a/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts b/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts index 7692eefae8..5963c712f2 100644 --- a/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts +++ b/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts @@ -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'; diff --git a/packages/asset-swapper/test/quote_simulation_test.ts b/packages/asset-swapper/test/quote_simulation_test.ts index c934f23d79..07493b47d6 100644 --- a/packages/asset-swapper/test/quote_simulation_test.ts +++ b/packages/asset-swapper/test/quote_simulation_test.ts @@ -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) diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index f2cd6dc01b..7dfbd991e6 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -41,6 +41,10 @@ { "note": "Redeploy `KyberBridge` on Mainnet", "pr": 2683 + }, + { + "note": "Update transformer deployer and transformers for champagne-problems deployment", + "pr": 2693 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 224a8237fb..ab924af787 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -38,15 +38,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3", "mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0xa8c8cf29699f223766f47fe79e2b7eb1a90e08c8", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", + "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", + "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", + "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" } }, "3": { @@ -88,15 +88,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x58faf8e7bad3131d00d4deacb7488744818b9570", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", + "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", + "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" } }, "4": { @@ -138,15 +138,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x2ebe42dab6c6dec58ba494d111d1464836216c2a", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", + "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", + "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" } }, "42": { @@ -188,15 +188,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x1b62de2dbb5e7aa519e9c442721ecef75702807f", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x10c394406d2b15fb8e67b9a7a0dd03fa4d3e8099", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d", + "payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977", + "affiliateFeeTransformer": "0x870893920a96a28d4b63c0a7d06a521e3bd074b3", + "fillQuoteTransformer": "0x71f5b09fe71d496c9e4f24d4de6aba21bf04b000" } }, "1337": { @@ -243,10 +243,10 @@ "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a", + "wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5", "payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3", - "fillQuoteTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5", - "affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db" + "affiliateFeeTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a", + "fillQuoteTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db" } } } diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 3c908e1336..c8c72da14d 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "5.2.0", + "changes": [ + { + "note": "Add `exchangeProxyMetaTransactionSchema`", + "pr": 2657 + } + ] + }, { "version": "5.1.0", "changes": [ diff --git a/packages/json-schemas/schemas/exchange_proxy_meta_transaction_schema.json b/packages/json-schemas/schemas/exchange_proxy_meta_transaction_schema.json new file mode 100644 index 0000000000..7c9a5a7d98 --- /dev/null +++ b/packages/json-schemas/schemas/exchange_proxy_meta_transaction_schema.json @@ -0,0 +1,30 @@ +{ + "id": "/exchangeProxyMetaTransactionSchema", + "properties": { + "signer": { "$ref": "/addressSchema" }, + "sender": { "$ref": "/addressSchema" }, + "minGasPrice": { "$ref": "/wholeNumberSchema" }, + "maxGasPrice": { "$ref": "/wholeNumberSchema" }, + "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }, + "salt": { "$ref": "/wholeNumberSchema" }, + "callData": { "$ref": "/hexSchema" }, + "value": { "$ref": "/wholeNumberSchema" }, + "feeToken": { "$ref": "/addressSchema" }, + "feeAmount": { "$ref": "/wholeNumberSchema" }, + "domain": { "$ref": "/eip712DomainSchema" } + }, + "required": [ + "signer", + "sender", + "minGasPrice", + "maxGasPrice", + "expirationTimeSeconds", + "salt", + "callData", + "value", + "feeToken", + "feeAmount", + "domain" + ], + "type": "object" +} diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts index 23836257b1..2e306e5939 100644 --- a/packages/json-schemas/src/schemas.ts +++ b/packages/json-schemas/src/schemas.ts @@ -7,6 +7,7 @@ import * as ecSignatureParameterSchema from '../schemas/ec_signature_parameter_s import * as ecSignatureSchema from '../schemas/ec_signature_schema.json'; import * as eip712DomainSchema from '../schemas/eip712_domain_schema.json'; import * as eip712TypedDataSchema from '../schemas/eip712_typed_data_schema.json'; +import * as exchangeProxyMetaTransactionSchema from '../schemas/exchange_proxy_meta_transaction_schema.json'; import * as hexSchema from '../schemas/hex_schema.json'; import * as indexFilterValuesSchema from '../schemas/index_filter_values_schema.json'; import * as jsNumber from '../schemas/js_number_schema.json'; @@ -87,5 +88,6 @@ export const schemas = { relayerApiOrdersResponseSchema, relayerApiAssetDataPairsSchema, zeroExTransactionSchema, + exchangeProxyMetaTransactionSchema, wholeNumberSchema, }; diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index 75fe2277af..430d3155d9 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -50,6 +50,7 @@ "./schemas/orderbook_request_schema.json", "./schemas/orders_request_opts_schema.json", "./schemas/paged_request_opts_schema.json", - "./schemas/order_config_request_schema.json" + "./schemas/order_config_request_schema.json", + "./schemas/exchange_proxy_meta_transaction_schema.json" ] } diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 7430065450..25a4266ec0 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -13,6 +13,18 @@ { "note": "Add bancorBridge to addresses", "pr": 2650 + }, + { + "note": "Update EP migration.", + "pr": 2657 + }, + { + "note": "Add mooniswap addresses to `BridgeAdapter` deployment", + "pr": 2692 + }, + { + "note": "Reorder transformer deployments", + "pr": 2693 } ] }, diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 30b4734b16..90fb275ff9 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -31,8 +31,6 @@ import { BridgeAdapterContract, FillQuoteTransformerContract, fullMigrateAsync as fullMigrateExchangeProxyAsync, - ITokenSpenderContract, - ITransformERC20Contract, PayTakerTransformerContract, WethTransformerContract, } from '@0x/contracts-zero-ex'; @@ -316,6 +314,7 @@ export async function runMigrationsAsync( balancerBridge: NULL_ADDRESS, curveBridge: NULL_ADDRESS, kyberBridge: NULL_ADDRESS, + mooniswapBridge: NULL_ADDRESS, mStableBridge: NULL_ADDRESS, oasisBridge: NULL_ADDRESS, uniswapBridge: NULL_ADDRESS, @@ -330,25 +329,16 @@ export async function runMigrationsAsync( ); const exchangeProxy = await fullMigrateExchangeProxyAsync(txDefaults.from, provider, txDefaults); - const exchangeProxyAllowanceTargetAddress = await new ITokenSpenderContract( - exchangeProxy.address, - provider, - txDefaults, - ) - .getAllowanceTarget() - .callAsync(); - const exchangeProxyFlashWalletAddress = await new ITransformERC20Contract(exchangeProxy.address, provider) - .getTransformWallet() - .callAsync(); + const exchangeProxyAllowanceTargetAddress = await exchangeProxy.getAllowanceTarget().callAsync(); + const exchangeProxyFlashWalletAddress = await exchangeProxy.getTransformWallet().callAsync(); // Deploy transformers. - const fillQuoteTransformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync( - exchangeProxyArtifacts.FillQuoteTransformer, + const wethTransformer = await WethTransformerContract.deployFrom0xArtifactAsync( + exchangeProxyArtifacts.WethTransformer, provider, txDefaults, allArtifacts, - exchange.address, - bridgeAdapter.address, + etherToken.address, ); const payTakerTransformer = await PayTakerTransformerContract.deployFrom0xArtifactAsync( exchangeProxyArtifacts.PayTakerTransformer, @@ -356,18 +346,19 @@ export async function runMigrationsAsync( txDefaults, allArtifacts, ); - const wethTransformer = await WethTransformerContract.deployFrom0xArtifactAsync( - exchangeProxyArtifacts.WethTransformer, + const affiliateFeeTransformer = await AffiliateFeeTransformerContract.deployFrom0xArtifactAsync( + exchangeProxyArtifacts.AffiliateFeeTransformer, provider, txDefaults, allArtifacts, - etherToken.address, ); - const affiliateFeeTransformer = await AffiliateFeeTransformerContract.deployFrom0xArtifactAsync( - exchangeProxyArtifacts.AffiliateFeeTransformer, + const fillQuoteTransformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync( + exchangeProxyArtifacts.FillQuoteTransformer, provider, txDefaults, allArtifacts, + exchange.address, + bridgeAdapter.address, ); const contractAddresses = { diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index be2bacc702..fdee878b97 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "10.3.1", + "version": "10.4.0", "changes": [ { "note": "Add gitpkg.", @@ -9,6 +9,22 @@ { "note": "Fix `decodeAffiliateFeeTransformerData`", "pr": 2658 + }, + { + "note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.", + "pr": 2657 + }, + { + "note": "Add `findTransformerNonce()` and `getTransformerAddress()` functions.", + "pr": 2657 + }, + { + "note": "Fix EP signature utils schema assertion.", + "pr": 2657 + }, + { + "note": "Add `rfqtTakerAddress` to `FillQuoteTransformerData`", + "pr": 2692 } ] }, diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index bfc0d9506a..6a54e31604 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -77,7 +77,9 @@ export { AffiliateFeeTransformerData, encodeAffiliateFeeTransformerData, decodeAffiliateFeeTransformerData, -} from './transformer_data_encoders'; + findTransformerNonce, + getTransformerAddress, +} from './transformer_utils'; export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils'; diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 8058a2cbf2..993030a44b 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -206,7 +206,9 @@ export const signatureUtils = { transaction: ExchangeProxyMetaTransaction, signerAddress: string, ): Promise { - assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]); + assert.doesConformToSchema('transaction', transaction, schemas.exchangeProxyMetaTransactionSchema, [ + schemas.hexSchema, + ]); try { const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync( supportedProvider, @@ -253,7 +255,9 @@ export const signatureUtils = { ): Promise { const provider = providerUtils.standardizeOrThrow(supportedProvider); assert.isETHAddressHex('signerAddress', signerAddress); - assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]); + assert.doesConformToSchema('transaction', transaction, schemas.exchangeProxyMetaTransactionSchema, [ + schemas.hexSchema, + ]); const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); diff --git a/packages/order-utils/src/transformer_data_encoders.ts b/packages/order-utils/src/transformer_utils.ts similarity index 79% rename from packages/order-utils/src/transformer_data_encoders.ts rename to packages/order-utils/src/transformer_utils.ts index cb3bb17d11..9bdd93b67b 100644 --- a/packages/order-utils/src/transformer_data_encoders.ts +++ b/packages/order-utils/src/transformer_utils.ts @@ -1,5 +1,10 @@ import { Order } from '@0x/types'; import { AbiEncoder, BigNumber } from '@0x/utils'; +import * as ethjs from 'ethereumjs-util'; + +import { constants } from './constants'; + +const { NULL_ADDRESS } = constants; const ORDER_ABI_COMPONENTS = [ { name: 'makerAddress', type: 'address' }, @@ -37,6 +42,8 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([ { name: 'signatures', type: 'bytes[]' }, { name: 'maxOrderFillAmounts', type: 'uint256[]' }, { name: 'fillAmount', type: 'uint256' }, + { name: 'refundReceiver', type: 'address' }, + { name: 'rfqtTakerAddress', type: 'address' }, ], }, ]); @@ -60,6 +67,8 @@ export interface FillQuoteTransformerData { signatures: string[]; maxOrderFillAmounts: BigNumber[]; fillAmount: BigNumber; + refundReceiver: string; + rfqtTakerAddress: string; } /** @@ -185,3 +194,36 @@ export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerD export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData { return affiliateFeeTransformerDataEncoder.decode(encoded); } + +/** + * Find the nonce for a transformer given its deployer. + * If `deployer` is the null address, zero will always be returned. + */ +export function findTransformerNonce( + transformer: string, + deployer: string = NULL_ADDRESS, + maxGuesses: number = 1024, +): number { + if (deployer === NULL_ADDRESS) { + return 0; + } + const lowercaseTransformer = transformer.toLowerCase(); + // Try to guess the nonce. + for (let nonce = 0; nonce < maxGuesses; ++nonce) { + const deployedAddress = getTransformerAddress(deployer, nonce); + if (deployedAddress === lowercaseTransformer) { + return nonce; + } + } + throw new Error(`${deployer} did not deploy ${transformer}!`); +} + +/** + * Compute the deployed address for a transformer given a deployer and nonce. + */ +export function getTransformerAddress(deployer: string, nonce: number): string { + return ethjs.bufferToHex( + // tslint:disable-next-line: custom-no-magic-numbers + ethjs.rlphash([deployer, nonce] as any).slice(12), + ); +} diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 1fa4f2738f..2ccbfca7dc 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Added support for nested rich revert decoding", "pr": 2668 + }, + { + "note": "Add EP flavor of `IllegalReentrancyError`.", + "pr": 2657 } ] }, diff --git a/packages/utils/src/revert_error.ts b/packages/utils/src/revert_error.ts index 3fa2c6213b..15d5378a9c 100644 --- a/packages/utils/src/revert_error.ts +++ b/packages/utils/src/revert_error.ts @@ -7,7 +7,7 @@ import { inspect } from 'util'; import * as AbiEncoder from './abi_encoder'; import { BigNumber } from './configured_bignumber'; -// tslint:disable: max-classes-per-file +// tslint:disable: max-classes-per-file no-unnecessary-type-assertion type ArgTypes = | string @@ -109,7 +109,10 @@ export abstract class RevertError extends Error { * @param coerce Whether to coerce unknown selectors into a `RawRevertError` type. * @return A RevertError object. */ - public static decode(bytes: string | Buffer, coerce: boolean = false): RevertError { + public static decode(bytes: string | Buffer | RevertError, coerce: boolean = false): RevertError { + if (bytes instanceof RevertError) { + return bytes; + } const _bytes = bytes instanceof Buffer ? ethUtil.bufferToHex(bytes) : ethUtil.addHexPrefix(bytes); // tslint:disable-next-line: custom-no-magic-numbers const selector = _bytes.slice(2, 10); @@ -122,21 +125,7 @@ export abstract class RevertError extends Error { const { type, decoder } = RevertError._typeRegistry[selector]; const instance = new type(); try { - const values = decoder(_bytes); - _.transform( - values, - (result, value, key) => { - const { type: argType } = instance._getArgumentByName(key); - if (argType === 'bytes') { - try { - const nestedRevert = RevertError.decode(value as string, coerce); - result[key] = nestedRevert.toString(); - } catch (err) {} // tslint:disable-line:no-empty - } - }, - values, - ); - _.assign(instance, { values }); + Object.assign(instance, { values: decoder(_bytes) }); instance.message = instance.toString(); return instance; } catch (err) { @@ -306,6 +295,16 @@ export abstract class RevertError extends Error { return `${this.constructor.name}(${this._raw})`; } const values = _.omitBy(this.values, (v: any) => _.isNil(v)); + // tslint:disable-next-line: forin + for (const k in values) { + const { type: argType } = this._getArgumentByName(k); + if (argType === 'bytes') { + // Try to decode nested revert errors. + try { + values[k] = RevertError.decode(values[k] as any); + } catch (err) {} // tslint:disable-line:no-empty + } + } const inner = _.isEmpty(values) ? '' : inspect(values); return `${this.constructor.name}(${inner})`; } @@ -498,6 +497,12 @@ function declarationToAbi(declaration: string): RevertErrorAbi { } function checkArgEquality(type: string, lhs: ArgTypes, rhs: ArgTypes): boolean { + // Try to compare as decoded revert errors first. + try { + return RevertError.decode(lhs as any).equals(RevertError.decode(rhs as any)); + } catch (err) { + // no-op + } if (type === 'address') { return normalizeAddress(lhs as string) === normalizeAddress(rhs as string); } else if (type === 'bytes' || /^bytes(\d+)$/.test(type)) { diff --git a/packages/utils/src/revert_errors/staking/staking_revert_errors.ts b/packages/utils/src/revert_errors/staking/staking_revert_errors.ts index 7cd5a2acd2..82a3816c3b 100644 --- a/packages/utils/src/revert_errors/staking/staking_revert_errors.ts +++ b/packages/utils/src/revert_errors/staking/staking_revert_errors.ts @@ -40,9 +40,9 @@ export class OnlyCallableByExchangeError extends RevertError { } export class ExchangeManagerError extends RevertError { - constructor(error?: ExchangeManagerErrorCodes, senderAddress?: string) { + constructor(errorCode?: ExchangeManagerErrorCodes, senderAddress?: string) { super('ExchangeManagerError', 'ExchangeManagerError(uint8 errorCode, address senderAddress)', { - error, + errorCode, senderAddress, }); } diff --git a/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts index c3f4091ee8..67df68fff0 100644 --- a/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts +++ b/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts @@ -1,4 +1,5 @@ import { RevertError } from '../../revert_error'; +import { Numberish } from '../../types'; // tslint:disable:max-classes-per-file export class OnlyCallableBySelfError extends RevertError { @@ -9,14 +10,16 @@ export class OnlyCallableBySelfError extends RevertError { } } -// This is identical to the one in utils. -// export class IllegalReentrancyError extends RevertError { -// constructor() { -// super('IllegalReentrancyError', 'IllegalReentrancyError()', {}); -// } -// } +export class IllegalReentrancyError extends RevertError { + constructor(selector?: string, reentrancyFlags?: Numberish) { + super('IllegalReentrancyError', 'IllegalReentrancyError(bytes4 selector, uint256 reentrancyFlags)', { + selector, + reentrancyFlags, + }); + } +} -const types = [OnlyCallableBySelfError]; +const types = [OnlyCallableBySelfError, IllegalReentrancyError]; // Register the types we've defined. for (const type of types) { diff --git a/packages/utils/test/revert_error_test.ts b/packages/utils/test/revert_error_test.ts index b6618e7651..2f2796bea2 100644 --- a/packages/utils/test/revert_error_test.ts +++ b/packages/utils/test/revert_error_test.ts @@ -170,7 +170,7 @@ describe('RevertError', () => { const nested = new StringRevertError(message); const parent = new ParentRevertError(nested.encode()); const decoded = RevertError.decode(parent.encode()); - expect(decoded.toString()).to.equal(new ParentRevertError(nested.toString()).toString()); + expect(decoded.encode()).to.equal(new ParentRevertError(nested.encode()).encode()); }); }); describe('getThrownErrorRevertErrorBytes', () => {