From 41907936a1786af90a817182c925b9941fd8cda5 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 6 Jul 2020 23:15:37 -0400 Subject: [PATCH 01/47] `@0x/contracts-zero-ex`: Fix `TransformerDeployer.kill()` calling the wrong `die()` interface. --- contracts/zero-ex/CHANGELOG.json | 4 ++++ .../src/external/TransformerDeployer.sol | 9 ++++++--- .../test/TestTransformerDeployerTransformer.sol | 9 +++++++-- .../zero-ex/test/transformer_deployer_test.ts | 15 ++++++++++++--- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index d567edd0fc..d153ff17bb 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Export `AffiliateFeeTransformerContract`", "pr": 2622 + }, + { + "note": "Fix `TransformerDeployer.kill()` calling the wrong `die()` interface.", + "pr": 2624 } ] }, diff --git a/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol b/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol index 3e3243605c..6e369e523d 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. @@ -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/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/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 }], From 5dd686f22fd028c90ee53b5706d9db71764e6bb4 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 5 Aug 2020 13:33:07 -0400 Subject: [PATCH 02/47] `@0x/contracts-zero-ex`: Add reentrancy guard to mtx functions `@0x/contracts-zero-ex`: Add refund mechanism to mtxs `@0x/contracts-zero-ex`: Pass sender to transfomers. `@0x/contracts-zero-ex`: Refund protocol fees to `refundReceiver` in FQT. `@0x/utils`: Add EP flavor of `IllegalReentrancyError` `@0x/order-utils`: Add `refundReceiver` to FQT transform data. `@0x/asset-swapper`: Add `refundReceiver` support to EP swap quote consumer. --- contracts/zero-ex/CHANGELOG.json | 21 +++ .../src/errors/LibCommonRichErrors.sol | 6 +- .../src/features/MetaTransactions.sol | 43 +++-- .../src/features/SignatureValidator.sol | 49 +++-- .../contracts/src/features/TransformERC20.sol | 9 +- .../src/fixins/FixinReentrancyGuard.sol | 60 +++++++ .../src/storage/LibReentrancyGuardStorage.sol | 46 +++++ .../contracts/src/storage/LibStorage.sol | 3 +- .../transformers/AffiliateFeeTransformer.sol | 10 +- .../src/transformers/FillQuoteTransformer.sol | 37 +++- .../src/transformers/IERC20Transformer.sol | 23 ++- .../src/transformers/PayTakerTransformer.sol | 13 +- .../src/transformers/WethTransformer.sol | 12 +- .../test/TestFillQuoteTransformerHost.sol | 12 +- ...tMetaTransactionsTransformERC20Feature.sol | 44 +++++ .../test/TestMintTokenERC20Transformer.sol | 22 ++- .../contracts/test/TestTransformerBase.sol | 8 +- .../contracts/test/TestTransformerHost.sol | 8 +- .../test/TestWethTransformerHost.sol | 10 +- contracts/zero-ex/package.json | 2 +- contracts/zero-ex/test/artifacts.ts | 4 + .../test/features/meta_transactions_test.ts | 168 +++++++++++++++++- .../test/features/transform_erc20_test.ts | 24 ++- .../affiliate_fee_transformer_test.ts | 21 ++- .../fill_quote_transformer_test.ts | 137 +++++++++++++- .../pay_taker_transformer_test.ts | 28 ++- contracts/zero-ex/test/wrappers.ts | 2 + contracts/zero-ex/tsconfig.json | 2 + packages/asset-swapper/CHANGELOG.json | 4 + packages/asset-swapper/src/index.ts | 1 + .../exchange_proxy_swap_quote_consumer.ts | 3 +- packages/asset-swapper/src/types.ts | 16 ++ packages/order-utils/CHANGELOG.json | 6 +- .../src/transformer_data_encoders.ts | 2 + packages/utils/CHANGELOG.json | 9 + .../zero-ex/common_revert_errors.ts | 17 +- 36 files changed, 753 insertions(+), 129 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/fixins/FixinReentrancyGuard.sol create mode 100644 contracts/zero-ex/contracts/src/storage/LibReentrancyGuardStorage.sol diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 69e75db6b6..ba26a2b504 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,25 @@ [ + { + "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 + } + ] + }, { "version": "0.2.0", "changes": [ 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/features/MetaTransactions.sol b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol index 975520a6a5..f9eca8960e 100644 --- a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol @@ -21,8 +21,10 @@ 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"; @@ -39,6 +41,7 @@ contract MetaTransactions is IFeature, IMetaTransactions, FixinCommon, + FixinReentrancyGuard, FixinEIP712 { using LibBytesV06 for bytes; @@ -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) { @@ -255,19 +272,9 @@ contract MetaTransactions is _validateMetaTransaction(state); // Mark the transaction executed. - assert(block.number > 0); 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( @@ -277,6 +284,16 @@ contract MetaTransactions is mtx.feeAmount ); } + + // 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(); + } emit MetaTransactionExecuted( state.hash, state.selector, @@ -367,7 +384,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 +411,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 { diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol index b7e6b1b71a..a8039af0e9 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol @@ -37,6 +37,13 @@ contract SignatureValidator is 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. @@ -160,12 +167,18 @@ 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 (v < 27) { + // Handle clients that encode v as 0 or 1. + v += 27; + } + 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 +192,21 @@ 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 (v < 27) { + // Handle clients that encode v as 0 or 1. + v += 27; + } + 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/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20.sol index e7bafaa355..a2fe5a48ff 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20.sol @@ -360,9 +360,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. 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/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..8ec1cd467e 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -58,18 +58,14 @@ contract AffiliateFeeTransformer is {} /// @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 3adbb48036..656a86549d 100644 --- a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol @@ -74,6 +74,12 @@ 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; } /// @dev Results of a call to `_fillOrder()`. @@ -111,6 +117,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,32 +142,28 @@ 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 freesGasTokensFromCollector 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(); } @@ -249,6 +257,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; } diff --git a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol index 40fd9387c0..5b079ed686 100644 --- a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol @@ -25,17 +25,24 @@ 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. + 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/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/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/TestMetaTransactionsTransformERC20Feature.sol b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol index 913fa414d7..5c7d8f1596 100644 --- a/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol @@ -20,6 +20,7 @@ pragma solidity ^0.6.5; pragma experimental ABIEncoderV2; import "../src/features/TransformERC20.sol"; +import "../src/features/IMetaTransactions.sol"; contract TestMetaTransactionsTransformERC20Feature is @@ -48,6 +49,49 @@ contract TestMetaTransactionsTransformERC20Feature is revert('FAIL'); } + if (msg.value == 777) { + // Try to reenter `executeMetaTransaction()` + IMetaTransactions(address(this)).executeMetaTransaction( + IMetaTransactions.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()` + IMetaTransactions.MetaTransactionData[] memory mtxs = + new IMetaTransactions.MetaTransactionData[](1); + bytes[] memory signatures = new bytes[](1); + mtxs[0] = IMetaTransactions.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] = ""; + IMetaTransactions(address(this)).batchExecuteMetaTransactions( + mtxs, + signatures + ); + } + emit TransformERC20Called( msg.sender, msg.value, 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/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/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 fed17c3770..c78bbba942 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -41,7 +41,7 @@ "config": { "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index c8db1d2135..93bec8a119 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -12,6 +12,7 @@ import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTran import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json'; import * as FixinEIP712 from '../test/generated-artifacts/FixinEIP712.json'; import * as FixinGasToken from '../test/generated-artifacts/FixinGasToken.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'; @@ -41,6 +42,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'; @@ -120,6 +122,7 @@ export const artifacts = { FixinCommon: FixinCommon as ContractArtifact, FixinEIP712: FixinEIP712 as ContractArtifact, FixinGasToken: FixinGasToken as ContractArtifact, + FixinReentrancyGuard: FixinReentrancyGuard as ContractArtifact, FullMigration: FullMigration as ContractArtifact, InitialMigration: InitialMigration as ContractArtifact, LibBootstrap: LibBootstrap as ContractArtifact, @@ -127,6 +130,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, diff --git a/contracts/zero-ex/test/features/meta_transactions_test.ts b/contracts/zero-ex/test/features/meta_transactions_test.ts index f5f30fda7f..c19f4a038c 100644 --- a/contracts/zero-ex/test/features/meta_transactions_test.ts +++ b/contracts/zero-ex/test/features/meta_transactions_test.ts @@ -36,6 +36,10 @@ blockchainTests.resets('MetaTransactions feature', env => { 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(); @@ -263,7 +267,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, @@ -469,6 +473,72 @@ blockchainTests.resets('MetaTransactions feature', env => { ), ); }); + + it('cannot reenter `executeMetaTransaction()`', async () => { + const args = getRandomTransformERC20Args(); + const mtx = getRandomMetaTransaction({ + callData: transformERC20Feature + .transformERC20( + args.inputToken, + args.outputToken, + args.inputTokenAmount, + args.minOutputTokenAmount, + args.transformations, + ) + .getABIEncodedTransactionData(), + value: TRANSFORM_ERC20_REENTER_VALUE, + }); + const mtxHash = getExchangeProxyMetaTransactionHash(mtx); + const signature = await signMetaTransactionAsync(mtx); + const callOpts = { + gasPrice: mtx.maxGasPrice, + value: mtx.value, + }; + const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( + mtxHash, + undefined, + new ZeroExRevertErrors.Common.IllegalReentrancyError( + feature.getSelector('executeMetaTransaction'), + REENTRANCY_FLAG_MTX, + ).encode(), + ), + ); + }); + + it('cannot reenter `batchExecuteMetaTransactions()`', async () => { + const args = getRandomTransformERC20Args(); + const mtx = getRandomMetaTransaction({ + callData: transformERC20Feature + .transformERC20( + args.inputToken, + args.outputToken, + args.inputTokenAmount, + args.minOutputTokenAmount, + args.transformations, + ) + .getABIEncodedTransactionData(), + value: TRANSFORM_ERC20_BATCH_REENTER_VALUE, + }); + const mtxHash = getExchangeProxyMetaTransactionHash(mtx); + const signature = await signMetaTransactionAsync(mtx); + const callOpts = { + gasPrice: mtx.maxGasPrice, + value: mtx.value, + }; + const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( + mtxHash, + undefined, + new ZeroExRevertErrors.Common.IllegalReentrancyError( + feature.getSelector('batchExecuteMetaTransactions'), + REENTRANCY_FLAG_MTX, + ).encode(), + ), + ); + }); }); describe('batchExecuteMetaTransactions()', () => { @@ -526,6 +596,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/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 4625325a9c..a0a28e399b 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -37,6 +37,7 @@ 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; @@ -44,7 +45,7 @@ blockchainTests.resets('TransformERC20 feature', env => { let allowanceTarget: string; before(async () => { - [owner, taker, transformerDeployer] = await env.getAccountAddressesAsync(); + [owner, taker, sender, transformerDeployer] = await env.getAccountAddressesAsync(); zeroEx = await fullMigrateAsync( owner, env.provider, @@ -59,12 +60,12 @@ blockchainTests.resets('TransformERC20 feature', env => { }, { transformerDeployer }, ); - feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis); + feature = new TransformERC20Contract(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) .getAllowanceTarget() .callAsync(); - await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync(); + await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync({ from: owner }); }); const { MAX_UINT256, ZERO_AMOUNT } = constants; @@ -73,7 +74,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); }); @@ -264,8 +265,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, + sender, taker, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -320,8 +322,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, taker, + sender, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -379,8 +382,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, + sender, taker, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -496,8 +500,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 +510,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, 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 92bd69d2ef..dc987a10ce 100644 --- a/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts @@ -31,6 +31,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; @@ -43,7 +45,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, @@ -69,7 +71,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( @@ -246,6 +248,7 @@ blockchainTests.resets('FillQuoteTransformer', env => { signatures: [], maxOrderFillAmounts: [], fillAmount: MAX_UINT256, + refundReceiver: NULL_ADDRESS, ...fields, }); } @@ -288,6 +291,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -309,6 +314,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -333,6 +340,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -355,6 +364,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -379,6 +390,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -405,6 +418,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -437,6 +452,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -461,6 +478,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, takerTokenBalance, + sender, + taker, encodeTransformData({ orders, signatures, @@ -510,6 +531,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, takerTokenBalance, + sender, + taker, encodeTransformData({ orders, signatures, @@ -539,6 +562,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -561,6 +586,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -583,6 +610,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -602,6 +631,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -615,6 +646,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', () => { @@ -627,6 +732,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -650,6 +757,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -676,6 +785,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -700,6 +811,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -726,6 +839,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -749,6 +864,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -779,6 +896,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -803,6 +922,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -827,6 +948,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -848,6 +971,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -875,6 +1000,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -901,6 +1028,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -927,6 +1056,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -955,6 +1086,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 5e74584ff7..44ac411a25 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -10,6 +10,7 @@ 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_gas_token'; +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'; @@ -39,6 +40,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'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index e92154d08e..cccbec79f6 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -31,6 +31,7 @@ "test/generated-artifacts/FixinCommon.json", "test/generated-artifacts/FixinEIP712.json", "test/generated-artifacts/FixinGasToken.json", + "test/generated-artifacts/FixinReentrancyGuard.json", "test/generated-artifacts/FlashWallet.json", "test/generated-artifacts/FullMigration.json", "test/generated-artifacts/IAllowanceTarget.json", @@ -60,6 +61,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", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 72ebb1b118..3943c1693f 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -33,6 +33,10 @@ { "note": "Add support for buy token affiliate fees", "pr": 2658 + }, + { + "note": "Add `refundReceiver` to `ExchangeProxySwapQuoteConsumer` options.", + "pr": 2657 } ] }, diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 1f75620bd2..65505e73e0 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -43,6 +43,7 @@ export { AffiliateFee, CalldataInfo, ExchangeProxyContractOpts, + ExchangeProxyRefundReceiver, ExtensionContractType, ForwarderExtensionContractOpts, GetExtensionContractTypeOpts, 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 5fb2c44f63..921b18b11f 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 @@ -86,7 +86,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; @@ -114,6 +114,7 @@ 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: [], diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index bd193ef406..35d9ddce60 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -130,15 +130,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 type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote; diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index be2bacc702..290563e85a 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,10 @@ { "note": "Fix `decodeAffiliateFeeTransformerData`", "pr": 2658 + }, + { + "note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.", + "pr": 2657 } ] }, diff --git a/packages/order-utils/src/transformer_data_encoders.ts b/packages/order-utils/src/transformer_data_encoders.ts index cb3bb17d11..8f747bdbe7 100644 --- a/packages/order-utils/src/transformer_data_encoders.ts +++ b/packages/order-utils/src/transformer_data_encoders.ts @@ -37,6 +37,7 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([ { name: 'signatures', type: 'bytes[]' }, { name: 'maxOrderFillAmounts', type: 'uint256[]' }, { name: 'fillAmount', type: 'uint256' }, + { name: 'refundReceiver', type: 'address' }, ], }, ]); @@ -60,6 +61,7 @@ export interface FillQuoteTransformerData { signatures: string[]; maxOrderFillAmounts: BigNumber[]; fillAmount: BigNumber; + refundReceiver: string; } /** diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index d7e95fd53c..37530885a8 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "5.6.0", + "changes": [ + { + "note": "Add EP flavor of `IllegalReentrancyError`.", + "pr": 2657 + } + ] + }, { "timestamp": 1594788383, "version": "5.5.1", 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) { From 34f516a41795a6d1e67c2454f03f73471f24a988 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 6 Aug 2020 22:32:42 -0400 Subject: [PATCH 03/47] `@0x/contracts-zero-ex`: Remove bugged parity signature compatibility. --- .../zero-ex/contracts/src/features/SignatureValidator.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol index a8039af0e9..aeb0666844 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol @@ -167,10 +167,6 @@ contract SignatureValidator is uint8 v = uint8(signature[0]); bytes32 r = signature.readBytes32(1); bytes32 s = signature.readBytes32(33); - if (v < 27) { - // Handle clients that encode v as 0 or 1. - v += 27; - } if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) { recovered = ecrecover( hash, @@ -192,10 +188,6 @@ contract SignatureValidator is uint8 v = uint8(signature[0]); bytes32 r = signature.readBytes32(1); bytes32 s = signature.readBytes32(33); - if (v < 27) { - // Handle clients that encode v as 0 or 1. - v += 27; - } if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) { recovered = ecrecover( keccak256(abi.encodePacked( From 10aceb164d2cb9f6f75a845e0013e4b262c6a0f7 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 6 Aug 2020 22:48:55 -0400 Subject: [PATCH 04/47] `@0x/asset-swapper`: Fix rebase conflicts --- packages/asset-swapper/src/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/asset-swapper/src/constants.ts b/packages/asset-swapper/src/constants.ts index d24ff0154a..61f44612cc 100644 --- a/packages/asset-swapper/src/constants.ts +++ b/packages/asset-swapper/src/constants.ts @@ -70,6 +70,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; From e27e6baabecd8a7dcb64570ecf0c2cc63e0b2636 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 17 Aug 2020 22:08:25 -0400 Subject: [PATCH 05/47] `@0x/contracts-integrations`: Add EP mtx tests. --- .../integrations/test/exchange-proxy/mtx_test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 contracts/integrations/test/exchange-proxy/mtx_test.ts 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..a87be54917 --- /dev/null +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -0,0 +1,11 @@ +import { blockchainTests, describe } from '@0x/contracts-test-utils'; + +blockchainTests.resets('exchange proxy - meta-transactions', env => { + before(async () => { + + }); + + it('can call `transformERC20()` with signed calldata', async () => { + + }); +}); From 8e73ae3614666671895eade7eebb2b34fd498aa3 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 18 Aug 2020 16:39:59 -0400 Subject: [PATCH 06/47] `@0x/order-utils`: Add `findTransformerNonce()` and `getTransformerAddress()` functions. --- packages/order-utils/CHANGELOG.json | 4 ++ packages/order-utils/src/index.ts | 4 +- ..._data_encoders.ts => transformer_utils.ts} | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) rename packages/order-utils/src/{transformer_data_encoders.ts => transformer_utils.ts} (81%) diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 290563e85a..340541034b 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -13,6 +13,10 @@ { "note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.", "pr": 2657 + }, + { + "note": "Add `findTransformerNonce()` and `getTransformerAddress()` functions.", + "pr": 2657 } ] }, 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/transformer_data_encoders.ts b/packages/order-utils/src/transformer_utils.ts similarity index 81% rename from packages/order-utils/src/transformer_data_encoders.ts rename to packages/order-utils/src/transformer_utils.ts index 8f747bdbe7..1383558cb5 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' }, @@ -187,3 +192,36 @@ export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerD export function decodeAffiliateFeeTransformerData(encoded: string): AffiliateFeeTransformerData { return affiliateFeeTransformerDataEncoder.decode(encoded); } + +/** + * Find the nonce for a transformer given its deployer. + * If `deployer` is the null address, zero will always be returned. + */ +export function findTransformerNonce( + transformer: string, + deployer: string = NULL_ADDRESS, + maxGuesses: number = 1024, +): number { + if (deployer === NULL_ADDRESS) { + return 0; + } + const lowercaseTransformer = transformer.toLowerCase(); + // Try to guess the nonce. + for (let nonce = 0; nonce < maxGuesses; ++nonce) { + const deployedAddress = getTransformerAddress(deployer, nonce); + if (deployedAddress === lowercaseTransformer) { + return nonce; + } + } + throw new Error(`${deployer} did not deploy ${transformer}!`); +} + +/** + * Compute the deployed address for a transformer given a deployer and nonce. + */ +export function getTransformerAddress(deployer: string, nonce: number): string { + return ethjs.bufferToHex( + // tslint:disable-next-line: custom-no-magic-numbers + ethjs.rlphash([deployer, nonce] as any).slice(12), + ); +} From 6aa8f17ed580cdbef9ea365a71244fea8fda93c5 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 19 Aug 2020 11:39:49 -0400 Subject: [PATCH 07/47] `@0x/json-schemas`: Add `exchangeProxyMetaTransactionSchema`. --- packages/json-schemas/CHANGELOG.json | 9 ++++++ ...xchange_proxy_meta_transaction_schema.json | 30 +++++++++++++++++++ packages/json-schemas/src/schemas.ts | 2 ++ packages/json-schemas/tsconfig.json | 3 +- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/json-schemas/schemas/exchange_proxy_meta_transaction_schema.json 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" ] } From d31d646ecc73bf87a20276a603390db775119655 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 19 Aug 2020 12:14:26 -0400 Subject: [PATCH 08/47] `@0x/order-utils`: Fix EP signature utils schema assertion. --- packages/order-utils/CHANGELOG.json | 4 ++++ packages/order-utils/src/signature_utils.ts | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 340541034b..f1dd56d75f 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -17,6 +17,10 @@ { "note": "Add `findTransformerNonce()` and `getTransformerAddress()` functions.", "pr": 2657 + }, + { + "note": "Fix EP signature utils schema assertion.", + "pr": 2657 } ] }, 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(); From 14d6330b40fed3f9ccaafdb19878c26d5d5eff0e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 19 Aug 2020 12:48:16 -0400 Subject: [PATCH 09/47] `@0x/asset-swaper`: Use transformer utils from `@0x/order-utils`. --- .../exchange_proxy_swap_quote_consumer.ts | 32 +------------------ ...exchange_proxy_swap_quote_consumer_test.ts | 6 ++-- 2 files changed, 3 insertions(+), 35 deletions(-) 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 921b18b11f..dc88874b58 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 @@ -9,11 +9,11 @@ import { ERC20AssetData, ETH_TOKEN_ADDRESS, FillQuoteTransformerSide, + findTransformerNonce, } from '@0x/order-utils'; import { AssetProxyId } from '@0x/types'; 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'; @@ -34,7 +34,6 @@ import { assert } from '../utils/assert'; // 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; @@ -210,32 +209,3 @@ function getTokenFromAssetData(assetData: string): string { // tslint:disable-next-line:no-unnecessary-type-assertion return (data as ERC20AssetData).tokenAddress; } - -/** - * 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/test/exchange_proxy_swap_quote_consumer_test.ts b/packages/asset-swapper/test/exchange_proxy_swap_quote_consumer_test.ts index 03052506fc..7f5ce6780d 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'; From 71700e69af2a4c23963c7832feefd9c1a3c5dae8 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 19 Aug 2020 12:50:20 -0400 Subject: [PATCH 10/47] `@0x/zero-ex`: Address CD post-audit feedback. Add `LogMetadataTransformer`. --- contracts/zero-ex/CHANGELOG.json | 8 ++++ .../contracts/src/features/Bootstrap.sol | 1 + .../src/features/MetaTransactions.sol | 4 +- .../contracts/src/features/Ownable.sol | 4 -- .../src/features/SignatureValidator.sol | 4 -- .../src/features/SimpleFunctionRegistry.sol | 4 -- .../contracts/src/features/TokenSpender.sol | 4 -- .../contracts/src/features/TransformERC20.sol | 13 ++---- .../transformers/AffiliateFeeTransformer.sol | 7 --- .../transformers/LogMetadataTransformer.sol | 46 +++++++++++++++++++ contracts/zero-ex/package.json | 4 +- contracts/zero-ex/src/artifacts.ts | 2 + contracts/zero-ex/src/index.ts | 1 + contracts/zero-ex/src/wrappers.ts | 1 + contracts/zero-ex/test/artifacts.ts | 2 + contracts/zero-ex/test/wrappers.ts | 1 + contracts/zero-ex/tsconfig.json | 2 + 17 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/transformers/LogMetadataTransformer.sol diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index ba26a2b504..6b747ca005 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -17,6 +17,14 @@ { "note": "Refund unused protocol fees to `refundReceiver` in FQT", "pr": 2657 + }, + { + "note": "Address CD post-audit feedback", + "pr": 2657 + }, + { + "note": "Add `LogMetadataTransformer`", + "pr": 2657 } ] }, diff --git a/contracts/zero-ex/contracts/src/features/Bootstrap.sol b/contracts/zero-ex/contracts/src/features/Bootstrap.sol index 3a65797818..14d2e9fe48 100644 --- a/contracts/zero-ex/contracts/src/features/Bootstrap.sol +++ b/contracts/zero-ex/contracts/src/features/Bootstrap.sol @@ -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/MetaTransactions.sol b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol index f9eca8960e..c75e645255 100644 --- a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol @@ -271,7 +271,9 @@ contract MetaTransactions is _validateMetaTransaction(state); - // Mark the transaction executed. + // Mark the transaction executed by storing the block at which it was executed. + // Currently the block number just indicates that the mtx was executed and + // serves no other purpose from within this contract. LibMetaTransactionsStorage.getStorage() .mtxHashToExecutedBlockNumber[state.hash] = block.number; diff --git a/contracts/zero-ex/contracts/src/features/Ownable.sol b/contracts/zero-ex/contracts/src/features/Ownable.sol index e6ad228178..2d75780b78 100644 --- a/contracts/zero-ex/contracts/src/features/Ownable.sol +++ b/contracts/zero-ex/contracts/src/features/Ownable.sol @@ -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 diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol index aeb0666844..13636807de 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol @@ -49,10 +49,6 @@ contract SignatureValidator is /// @dev Version of this feature. uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); - constructor() public FixinCommon() { - // solhint-disable-next-line no-empty-blocks - } - /// @dev Initialize and register this feature. /// Should be delegatecalled by `Migrate.migrate()`. /// @return success `LibMigrate.SUCCESS` on success. diff --git a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol index f9da65102f..5c73c8da7f 100644 --- a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol +++ b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol @@ -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/TokenSpender.sol index 509dd24b95..14a2509d03 100644 --- a/contracts/zero-ex/contracts/src/features/TokenSpender.sol +++ b/contracts/zero-ex/contracts/src/features/TokenSpender.sol @@ -48,10 +48,6 @@ contract TokenSpender is using LibRichErrorsV06 for bytes; - constructor() public FixinCommon() { - // solhint-disable-next-line no-empty-blocks - } - /// @dev Initialize and register this feature. Should be delegatecalled /// into during a `Migrate.migrate()`. /// @param allowanceTarget An `allowanceTarget` instance, configured to have diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20.sol index a2fe5a48ff..a1a2466d09 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20.sol @@ -61,10 +61,6 @@ contract TransformERC20 is /// @dev Version of this feature. uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0); - constructor() public FixinCommon() { - // solhint-disable-next-line no-empty-blocks - } - /// @dev Initialize and register this feature. /// Should be delegatecalled by `Migrate.migrate()`. /// @param transformerDeployer The trusted deployer for transformers. @@ -257,16 +253,15 @@ contract TransformERC20 is // Compute how much output token has been transferred to the taker. state.takerOutputTokenBalanceAfter = LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker); - if (state.takerOutputTokenBalanceAfter > state.takerOutputTokenBalanceBefore) { - outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub( - state.takerOutputTokenBalanceBefore - ); - } else if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) { + if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) { LibTransformERC20RichErrors.NegativeTransformERC20OutputError( address(args.outputToken), state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter ).rrevert(); } + outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub( + state.takerOutputTokenBalanceBefore + ); // Ensure enough output token has been sent to the taker. if (outputTokenAmount < args.minOutputTokenAmount) { LibTransformERC20RichErrors.IncompleteTransformERC20Error( diff --git a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol index 8ec1cd467e..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,12 +50,6 @@ contract AffiliateFeeTransformer is uint256 private constant MAX_UINT256 = uint256(-1); - /// @dev Create this contract. - constructor() - public - Transformer() - {} - /// @dev Transfers tokens to recipients. /// @param context Context information. /// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`). 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/package.json b/contracts/zero-ex/package.json index c78bbba942..3a3ac463ee 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", + "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions,LogMetadataTransformer", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/src/artifacts.ts b/contracts/zero-ex/src/artifacts.ts index b2e9d5b32f..dc7fdae77b 100644 --- a/contracts/zero-ex/src/artifacts.ts +++ b/contracts/zero-ex/src/artifacts.ts @@ -17,6 +17,7 @@ import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunction import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json'; import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json'; import * as IZeroEx from '../generated-artifacts/IZeroEx.json'; +import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json'; import * as MetaTransactions from '../generated-artifacts/MetaTransactions.json'; import * as Ownable from '../generated-artifacts/Ownable.json'; import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json'; @@ -48,4 +49,5 @@ export const artifacts = { AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact, SignatureValidator: SignatureValidator as ContractArtifact, MetaTransactions: MetaTransactions as ContractArtifact, + LogMetadataTransformer: LogMetadataTransformer as ContractArtifact, }; diff --git a/contracts/zero-ex/src/index.ts b/contracts/zero-ex/src/index.ts index 20e030544c..36c653e2ba 100644 --- a/contracts/zero-ex/src/index.ts +++ b/contracts/zero-ex/src/index.ts @@ -9,6 +9,7 @@ export { ITokenSpenderContract, ITransformERC20Contract, IZeroExContract, + LogMetadataTransformerContract, PayTakerTransformerContract, WethTransformerContract, ZeroExContract, diff --git a/contracts/zero-ex/src/wrappers.ts b/contracts/zero-ex/src/wrappers.ts index 156e6816ec..69a5e1e1ff 100644 --- a/contracts/zero-ex/src/wrappers.ts +++ b/contracts/zero-ex/src/wrappers.ts @@ -15,6 +15,7 @@ export * from '../generated-wrappers/i_token_spender'; export * from '../generated-wrappers/i_transform_erc20'; export * from '../generated-wrappers/i_zero_ex'; export * from '../generated-wrappers/initial_migration'; +export * from '../generated-wrappers/log_metadata_transformer'; export * from '../generated-wrappers/meta_transactions'; export * from '../generated-wrappers/ownable'; export * from '../generated-wrappers/pay_taker_transformer'; diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 93bec8a119..6f8b252f40 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -53,6 +53,7 @@ import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpe import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json'; import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json'; +import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json'; import * as MetaTransactions from '../test/generated-artifacts/MetaTransactions.json'; import * as Ownable from '../test/generated-artifacts/Ownable.json'; import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json'; @@ -139,6 +140,7 @@ export const artifacts = { FillQuoteTransformer: FillQuoteTransformer as ContractArtifact, IERC20Transformer: IERC20Transformer as ContractArtifact, LibERC20Transformer: LibERC20Transformer as ContractArtifact, + LogMetadataTransformer: LogMetadataTransformer as ContractArtifact, PayTakerTransformer: PayTakerTransformer as ContractArtifact, Transformer: Transformer as ContractArtifact, WethTransformer: WethTransformer as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 44ac411a25..9b976b0c5f 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -51,6 +51,7 @@ export * from '../test/generated-wrappers/lib_token_spender_storage'; export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors'; export * from '../test/generated-wrappers/lib_transform_erc20_storage'; export * from '../test/generated-wrappers/lib_wallet_rich_errors'; +export * from '../test/generated-wrappers/log_metadata_transformer'; export * from '../test/generated-wrappers/meta_transactions'; export * from '../test/generated-wrappers/ownable'; export * from '../test/generated-wrappers/pay_taker_transformer'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index cccbec79f6..123d1a4ff2 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -15,6 +15,7 @@ "generated-artifacts/ITransformERC20.json", "generated-artifacts/IZeroEx.json", "generated-artifacts/InitialMigration.json", + "generated-artifacts/LogMetadataTransformer.json", "generated-artifacts/MetaTransactions.json", "generated-artifacts/Ownable.json", "generated-artifacts/PayTakerTransformer.json", @@ -72,6 +73,7 @@ "test/generated-artifacts/LibTransformERC20RichErrors.json", "test/generated-artifacts/LibTransformERC20Storage.json", "test/generated-artifacts/LibWalletRichErrors.json", + "test/generated-artifacts/LogMetadataTransformer.json", "test/generated-artifacts/MetaTransactions.json", "test/generated-artifacts/Ownable.json", "test/generated-artifacts/PayTakerTransformer.json", From 9b6703398ea99c3700d877bc5d30c39a82d47675 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 19 Aug 2020 12:50:54 -0400 Subject: [PATCH 11/47] `@0x/contracts-integrations`: Add EP mtx tests. --- contracts/integrations/package.json | 1 + .../test/exchange-proxy/mtx_test.ts | 301 +++++++++++++++++- 2 files changed, 298 insertions(+), 4 deletions(-) diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index 5dcadf5a4b..37f5ea43d4 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -102,6 +102,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 index a87be54917..d62037945c 100644 --- a/contracts/integrations/test/exchange-proxy/mtx_test.ts +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -1,11 +1,304 @@ -import { blockchainTests, describe } from '@0x/contracts-test-utils'; +import { ContractAddresses } from '@0x/contract-addresses'; +import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; +import { IExchangeContract } from '@0x/contracts-exchange'; +import { blockchainTests, constants, expect, getRandomPortion, verifyEventsFromLogs } from '@0x/contracts-test-utils'; +import { + artifacts as exchangeProxyArtifacts, + IZeroExContract, + LogMetadataTransformerContract, + signCallData, +} from '@0x/contracts-zero-ex'; +import { migrateOnceAsync } from '@0x/migrations'; +import { + assetDataUtils, + encodeFillQuoteTransformerData, + encodePayTakerTransformerData, + ETH_TOKEN_ADDRESS, + FillQuoteTransformerSide, + findTransformerNonce, + signatureUtils, + SignedExchangeProxyMetaTransaction, +} from '@0x/order-utils'; +import { AssetProxyId, Order, SignedOrder } from '@0x/types'; +import { BigNumber, hexUtils } from '@0x/utils'; +import * as ethjs from 'ethereumjs-util'; -blockchainTests.resets('exchange proxy - meta-transactions', env => { - before(async () => { +const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, NULL_BYTES32, ZERO_AMOUNT } = constants; + +blockchainTests.resets.only('exchange proxy - meta-transactions', env => { + const quoteSignerKey = hexUtils.random(); + const quoteSigner = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(quoteSignerKey))); + let owner: string; + let relayer: string; + let maker: string; + let taker: string; + let flashWalletAddress: string; + let zeroEx: IZeroExContract; + let exchange: IExchangeContract; + let inputToken: DummyERC20TokenContract; + let outputToken: DummyERC20TokenContract; + let feeToken: DummyERC20TokenContract; + let addresses: ContractAddresses; + let protocolFee: BigNumber; + let metadataTransformer: LogMetadataTransformerContract; + const GAS_PRICE = new BigNumber('1e9'); + const MAKER_BALANCE = new BigNumber('100e18'); + const TAKER_BALANCE = new BigNumber('100e18'); + const TAKER_FEE_BALANCE = new BigNumber('100e18'); + before(async () => { + [, relayer, maker, taker] = await env.getAccountAddressesAsync(); + addresses = await migrateOnceAsync(env.provider); + zeroEx = new IZeroExContract(addresses.exchangeProxy, env.provider, env.txDefaults, { + LogMetadataTransformer: LogMetadataTransformerContract.ABI(), + DummyERC20Token: DummyERC20TokenContract.ABI(), + }); + exchange = new IExchangeContract(addresses.exchange, env.provider, env.txDefaults); + [inputToken, outputToken, feeToken] = await Promise.all( + [...new Array(3)].map(i => + DummyERC20TokenContract.deployFrom0xArtifactAsync( + erc20Artifacts.DummyERC20Token, + env.provider, + env.txDefaults, + {}, + `DummyToken-${i}`, + `TOK${i}`, + new BigNumber(18), + BigNumber.max(MAKER_BALANCE, TAKER_BALANCE), + ), + ), + ); + // LogMetadataTransformer is not deployed in migrations. + metadataTransformer = await LogMetadataTransformerContract.deployFrom0xArtifactAsync( + exchangeProxyArtifacts.LogMetadataTransformer, + env.provider, + { + ...env.txDefaults, + from: addresses.exchangeProxyTransformerDeployer, + }, + {}, + ); + owner = await zeroEx.owner().callAsync(); + protocolFee = await exchange.protocolFeeMultiplier().callAsync(); + flashWalletAddress = await zeroEx.getTransformWallet().callAsync(); + const erc20Proxy = await exchange.getAssetProxy(AssetProxyId.ERC20).callAsync(); + const allowanceTarget = await zeroEx.getAllowanceTarget().callAsync(); + await outputToken.mint(MAKER_BALANCE).awaitTransactionSuccessAsync({ from: maker }); + await inputToken.mint(TAKER_BALANCE).awaitTransactionSuccessAsync({ from: taker }); + await feeToken.mint(TAKER_FEE_BALANCE).awaitTransactionSuccessAsync({ from: taker }); + await outputToken.approve(erc20Proxy, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker }); + await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }); + await feeToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }); + await zeroEx.setQuoteSigner(quoteSigner).awaitTransactionSuccessAsync({ from: owner }); }); - it('can call `transformERC20()` with signed calldata', async () => { + 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 = {}): 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: flashWalletAddress, + makerAddress: maker, + makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address), + takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address), + makerFeeAssetData: NULL_BYTES, + takerFeeAssetData: NULL_BYTES, + takerAssetAmount: getRandomPortion(TAKER_BALANCE), + makerAssetAmount: getRandomPortion(MAKER_BALANCE), + makerFee: ZERO_AMOUNT, + takerFee: ZERO_AMOUNT, + ...orderFields, + }, + maker, + ); + const transformations = [ + { + deploymentNonce: findTransformerNonce( + addresses.transformers.fillQuoteTransformer, + addresses.exchangeProxyTransformerDeployer, + ), + data: encodeFillQuoteTransformerData({ + orders: [order], + signatures: [order.signature], + buyToken: outputToken.address, + sellToken: inputToken.address, + fillAmount: order.takerAssetAmount, + maxOrderFillAmounts: [], + refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender. + side: FillQuoteTransformerSide.Sell, + }), + }, + { + deploymentNonce: findTransformerNonce( + addresses.transformers.payTakerTransformer, + addresses.exchangeProxyTransformerDeployer, + ), + data: encodePayTakerTransformerData({ + tokens: [inputToken.address, outputToken.address, ETH_TOKEN_ADDRESS], + amounts: [MAX_UINT256, MAX_UINT256, MAX_UINT256], + }), + }, + { + deploymentNonce: findTransformerNonce( + metadataTransformer.address, + addresses.exchangeProxyTransformerDeployer, + ), + data: NULL_BYTES, + }, + ]; + return { + transformations, + orders: [order], + inputTokenAddress: inputToken.address, + outputTokenAddress: outputToken.address, + inputTokenAmount: order.takerAssetAmount, + minOutputTokenAmount: order.makerAssetAmount, + }; + } + + function getSwapData(swap: SwapInfo): string { + return zeroEx + .transformERC20( + swap.inputTokenAddress, + swap.outputTokenAddress, + swap.inputTokenAmount, + swap.minOutputTokenAmount, + swap.transformations, + ) + .getABIEncodedTransactionData(); + } + + function getSignedSwapData(swap: SwapInfo, signerKey?: string): string { + return signCallData( + zeroEx + .transformERC20( + swap.inputTokenAddress, + swap.outputTokenAddress, + swap.inputTokenAmount, + swap.minOutputTokenAmount, + swap.transformations, + ) + .getABIEncodedTransactionData(), + signerKey ? signerKey : quoteSignerKey, + ); + } + + async function createMetaTransactionAsync( + data: string, + value: BigNumber, + ): Promise { + return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync( + env.provider, + { + value, + signer: taker, + sender: relayer, + minGasPrice: GAS_PRICE, + maxGasPrice: GAS_PRICE, + expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000) + 60), + salt: new BigNumber(hexUtils.random()), + callData: data, + feeToken: feeToken.address, + feeAmount: getRandomPortion(TAKER_FEE_BALANCE), + domain: { + chainId: 1, + name: 'ZeroEx', + version: '1.0.0', + verifyingContract: zeroEx.address, + }, + }, + taker, + ); + } + + it('can call `transformERC20()` with signed calldata and a relayer fee', async () => { + const swap = await generateSwapAsync(); + const callDataHash = hexUtils.hash(getSwapData(swap)); + const signedSwapData = getSignedSwapData(swap); + const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed. + const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee); + const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer); + const receipt = await zeroEx + .executeMetaTransaction(mtx, mtx.signature) + .awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE }); + const relayerEthRefund = relayerEthBalanceBefore + .minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer)) + .minus(GAS_PRICE.times(receipt.gasUsed)); + // Ensure the relayer got back the unused protocol fees. + expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE)); + // Ensure the relayer got paid mtx fees. + expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount); + // Ensure the taker got output tokens. + expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount); + // Ensure the maker got input tokens. + expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount); + // Check events. + verifyEventsFromLogs( + receipt.logs, + [ + { + taker, + callDataHash, + sender: zeroEx.address, + data: NULL_BYTES, + }, + ], + 'TransformerMetadata', + ); + }); + it('can call `transformERC20()` with wrongly signed calldata and a relayer fee', async () => { + const swap = await generateSwapAsync(); + const signedSwapData = getSignedSwapData(swap, hexUtils.random()); + const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed. + const mtx = await createMetaTransactionAsync(signedSwapData, _protocolFee); + const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer); + const receipt = await zeroEx + .executeMetaTransaction(mtx, mtx.signature) + .awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE }); + const relayerEthRefund = relayerEthBalanceBefore + .minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer)) + .minus(GAS_PRICE.times(receipt.gasUsed)); + // Ensure the relayer got back the unused protocol fees. + expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE)); + // Ensure the relayer got paid mtx fees. + expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount); + // Ensure the taker got output tokens. + expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount); + // Ensure the maker got input tokens. + expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount); + // Check events. + verifyEventsFromLogs( + receipt.logs, + [ + { + taker, + // Only signed calldata should have a nonzero hash. + callDataHash: NULL_BYTES32, + sender: zeroEx.address, + data: NULL_BYTES, + }, + ], + 'TransformerMetadata', + ); }); }); From e7ad7c3af780a7f3cecaf70740b4c025df9abcc3 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 20 Aug 2020 16:52:43 -0400 Subject: [PATCH 12/47] `@0x/contracts-zero-ex`: Add `IUniswapV2Feature`. `@0x/contracts-zero-ex`: Rename all feature contracts to have `Feature` suffix. `@0x/contracts-zero-ex`: Return an `IZeroExContract` instance from `fullMigrateAsync()`. --- contracts/zero-ex/CHANGELOG.json | 12 ++++ contracts/zero-ex/contracts/src/IZeroEx.sol | 24 ++++---- contracts/zero-ex/contracts/src/ZeroEx.sol | 6 +- .../{Bootstrap.sol => BootstrapFeature.sol} | 8 +-- .../{IBootstrap.sol => IBootstrapFeature.sol} | 2 +- ...tions.sol => IMetaTransactionsFeature.sol} | 2 +- .../{IOwnable.sol => IOwnableFeature.sol} | 2 +- ...tor.sol => ISignatureValidatorFeature.sol} | 2 +- ...sol => ISimpleFunctionRegistryFeature.sol} | 2 +- ...enSpender.sol => ITokenSpenderFeature.sol} | 2 +- ...rmERC20.sol => ITransformERC20Feature.sol} | 2 +- .../src/features/IUniswapV2Feature.sol | 49 ++++++++++++++++ ...ctions.sol => MetaTransactionsFeature.sol} | 32 +++++----- .../{Ownable.sol => OwnableFeature.sol} | 14 ++--- ...ator.sol => SignatureValidatorFeature.sol} | 6 +- ....sol => SimpleFunctionRegistryFeature.sol} | 6 +- ...kenSpender.sol => TokenSpenderFeature.sol} | 7 +-- ...ormERC20.sol => TransformERC20Feature.sol} | 17 +++--- .../contracts/src/fixins/FixinCommon.sol | 8 +-- .../contracts/src/fixins/FixinEIP712.sol | 1 - .../src/migrations/FullMigration.sol | 42 +++++++------- .../src/migrations/InitialMigration.sol | 26 ++++----- .../contracts/test/TestFullMigration.sol | 1 - .../contracts/test/TestInitialMigration.sol | 6 +- ...tMetaTransactionsTransformERC20Feature.sol | 18 +++--- .../zero-ex/contracts/test/TestMigrator.sol | 6 +- .../contracts/test/TestTokenSpender.sol | 4 +- .../contracts/test/TestTransformERC20.sol | 10 +--- contracts/zero-ex/package.json | 4 +- contracts/zero-ex/src/artifacts.ts | 40 ++++++------- contracts/zero-ex/src/index.ts | 12 ++-- contracts/zero-ex/src/migration.ts | 39 +++++++------ contracts/zero-ex/src/wrappers.ts | 20 +++---- contracts/zero-ex/test/artifacts.ts | 58 ++++++++++--------- .../test/features/meta_transactions_test.ts | 12 ++-- .../zero-ex/test/features/ownable_test.ts | 8 +-- .../test/features/signature_validator_test.ts | 8 +-- .../features/simple_function_registry_test.ts | 12 ++-- .../test/features/token_spender_test.ts | 10 ++-- .../test/features/transform_erc20_test.ts | 26 ++++----- contracts/zero-ex/test/full_migration_test.ts | 26 ++++----- .../zero-ex/test/initial_migration_test.ts | 18 +++--- contracts/zero-ex/test/wrappers.ts | 29 +++++----- contracts/zero-ex/test/zero_ex_test.ts | 12 ++-- contracts/zero-ex/tsconfig.json | 49 ++++++++-------- 45 files changed, 378 insertions(+), 322 deletions(-) rename contracts/zero-ex/contracts/src/features/{Bootstrap.sol => BootstrapFeature.sol} (95%) rename contracts/zero-ex/contracts/src/features/{IBootstrap.sol => IBootstrapFeature.sol} (97%) rename contracts/zero-ex/contracts/src/features/{IMetaTransactions.sol => IMetaTransactionsFeature.sol} (99%) rename contracts/zero-ex/contracts/src/features/{IOwnable.sol => IOwnableFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{ISignatureValidator.sol => ISignatureValidatorFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{ISimpleFunctionRegistry.sol => ISimpleFunctionRegistryFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{ITokenSpender.sol => ITokenSpenderFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{ITransformERC20.sol => ITransformERC20Feature.sol} (99%) create mode 100644 contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol rename contracts/zero-ex/contracts/src/features/{MetaTransactions.sol => MetaTransactionsFeature.sol} (94%) rename contracts/zero-ex/contracts/src/features/{Ownable.sol => OwnableFeature.sol} (89%) rename contracts/zero-ex/contracts/src/features/{SignatureValidator.sol => SignatureValidatorFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{SimpleFunctionRegistry.sol => SimpleFunctionRegistryFeature.sol} (98%) rename contracts/zero-ex/contracts/src/features/{TokenSpender.sol => TokenSpenderFeature.sol} (97%) rename contracts/zero-ex/contracts/src/features/{TransformERC20.sol => TransformERC20Feature.sol} (97%) diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 6b747ca005..c048793fb6 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -25,6 +25,18 @@ { "note": "Add `LogMetadataTransformer`", "pr": 2657 + }, + { + "note": "Add `IUniswapV2Feature`", + "pr": "TODO" + }, + { + "note": "Rename all feature contracts to have `Feature` suffix", + "pr": "TODO" + }, + { + "note": "Return `IZeroExContract` in `fullMigrateAsync()`", + "pr": "TODO" } ] }, 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/features/Bootstrap.sol b/contracts/zero-ex/contracts/src/features/BootstrapFeature.sol similarity index 95% rename from contracts/zero-ex/contracts/src/features/Bootstrap.sol rename to contracts/zero-ex/contracts/src/features/BootstrapFeature.sol index 14d2e9fe48..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); } 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/IUniswapV2Feature.sol b/contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol new file mode 100644 index 0000000000..eaab9e3ed5 --- /dev/null +++ b/contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol @@ -0,0 +1,49 @@ +/* + + 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/IERC20TokenV06.sol"; + + +/// @dev Feature for directly interacting with Uniswap V2 +interface IUniswapV2Feature { + + function sellToUniswapV2( + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + address recipient, + ) + external + payable + returns (uint256 outputTokenAmount); + + function buyFromUniswapV2( + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 maxInputTokenAmount, + uint256 outputTokenAmount, + address recipient, + ) + external + payable + returns (uint256 inputTokenAmount); +} diff --git a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol b/contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol similarity index 94% rename from contracts/zero-ex/contracts/src/features/MetaTransactions.sol rename to contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol index c75e645255..bd6e40b1c5 100644 --- a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/MetaTransactionsFeature.sol @@ -29,17 +29,17 @@ 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 @@ -72,7 +72,7 @@ contract MetaTransactions is IERC20TokenV06 outputToken; uint256 inputTokenAmount; uint256 minOutputTokenAmount; - ITransformERC20.Transformation[] transformations; + ITransformERC20Feature.Transformation[] transformations; } /// @dev Name of this feature. @@ -279,7 +279,7 @@ contract MetaTransactions is // 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. @@ -289,7 +289,7 @@ contract MetaTransactions is // Execute the call based on the selector. state.selector = mtx.callData.readBytes4(0); - if (state.selector == ITransformERC20.transformERC20.selector) { + if (state.selector == ITransformERC20Feature.transformERC20.selector) { returnResult = _executeTransformERC20Call(state); } else { LibMetaTransactionsRichErrors @@ -349,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) { @@ -372,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 @@ -426,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 89% rename from contracts/zero-ex/contracts/src/features/Ownable.sol rename to contracts/zero-ex/contracts/src/features/OwnableFeature.sol index 2d75780b78..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 { @@ -54,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 98% rename from contracts/zero-ex/contracts/src/features/SignatureValidator.sol rename to contracts/zero-ex/contracts/src/features/SignatureValidatorFeature.sol index 13636807de..cea91baacc 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidatorFeature.sol @@ -24,14 +24,14 @@ 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; diff --git a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistryFeature.sol similarity index 98% rename from contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol rename to contracts/zero-ex/contracts/src/features/SimpleFunctionRegistryFeature.sol index 5c73c8da7f..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. diff --git a/contracts/zero-ex/contracts/src/features/TokenSpender.sol b/contracts/zero-ex/contracts/src/features/TokenSpenderFeature.sol similarity index 97% rename from contracts/zero-ex/contracts/src/features/TokenSpender.sol rename to contracts/zero-ex/contracts/src/features/TokenSpenderFeature.sol index 14a2509d03..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 diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol similarity index 97% rename from contracts/zero-ex/contracts/src/features/TransformERC20.sol rename to contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol index a1a2466d09..f203a59d3e 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; @@ -209,7 +208,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); } @@ -313,7 +312,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, @@ -391,7 +390,7 @@ contract TransformERC20 is return bytes32(0); } - if (ISignatureValidator(address(this)).isValidHashSignature( + if (ISignatureValidatorFeature(address(this)).isValidHashSignature( callDataHash, getQuoteSigner(), signature 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/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/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 5c7d8f1596..bb31d76187 100644 --- a/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol @@ -19,12 +19,12 @@ pragma solidity ^0.6.5; pragma experimental ABIEncoderV2; -import "../src/features/TransformERC20.sol"; -import "../src/features/IMetaTransactions.sol"; +import "../src/features/TransformERC20Feature.sol"; +import "../src/features/IMetaTransactionsFeature.sol"; contract TestMetaTransactionsTransformERC20Feature is - TransformERC20 + TransformERC20Feature { event TransformERC20Called( address sender, @@ -51,8 +51,8 @@ contract TestMetaTransactionsTransformERC20Feature is if (msg.value == 777) { // Try to reenter `executeMetaTransaction()` - IMetaTransactions(address(this)).executeMetaTransaction( - IMetaTransactions.MetaTransactionData({ + IMetaTransactionsFeature(address(this)).executeMetaTransaction( + IMetaTransactionsFeature.MetaTransactionData({ signer: address(0), sender: address(0), minGasPrice: 0, @@ -70,10 +70,10 @@ contract TestMetaTransactionsTransformERC20Feature is if (msg.value == 888) { // Try to reenter `batchExecuteMetaTransactions()` - IMetaTransactions.MetaTransactionData[] memory mtxs = - new IMetaTransactions.MetaTransactionData[](1); + IMetaTransactionsFeature.MetaTransactionData[] memory mtxs = + new IMetaTransactionsFeature.MetaTransactionData[](1); bytes[] memory signatures = new bytes[](1); - mtxs[0] = IMetaTransactions.MetaTransactionData({ + mtxs[0] = IMetaTransactionsFeature.MetaTransactionData({ signer: address(0), sender: address(0), minGasPrice: 0, @@ -86,7 +86,7 @@ contract TestMetaTransactionsTransformERC20Feature is feeAmount: 0 }); signatures[0] = ""; - IMetaTransactions(address(this)).batchExecuteMetaTransactions( + IMetaTransactionsFeature(address(this)).batchExecuteMetaTransactions( mtxs, signatures ); 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/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/package.json b/contracts/zero-ex/package.json index 3a3ac463ee..1682f0b0ee 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,LogMetadataTransformer", + "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapV2Feature|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|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 dc7fdae77b..28c02cfd6d 100644 --- a/contracts/zero-ex/src/artifacts.ts +++ b/contracts/zero-ex/src/artifacts.ts @@ -12,19 +12,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 LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json'; -import * as MetaTransactions from '../generated-artifacts/MetaTransactions.json'; -import * as Ownable from '../generated-artifacts/Ownable.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 +35,19 @@ 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, }; diff --git a/contracts/zero-ex/src/index.ts b/contracts/zero-ex/src/index.ts index 36c653e2ba..742ff7e47c 100644 --- a/contracts/zero-ex/src/index.ts +++ b/contracts/zero-ex/src/index.ts @@ -2,12 +2,12 @@ export { artifacts } from './artifacts'; export { AffiliateFeeTransformerContract, FillQuoteTransformerContract, - IOwnableContract, - IOwnableEvents, - ISimpleFunctionRegistryContract, - ISimpleFunctionRegistryEvents, - ITokenSpenderContract, - ITransformERC20Contract, + IOwnableFeatureContract, + IOwnableFeatureEvents, + ISimpleFunctionRegistryFeatureContract, + ISimpleFunctionRegistryFeatureEvents, + ITokenSpenderFeatureContract, + ITransformERC20FeatureContract, IZeroExContract, LogMetadataTransformerContract, PayTakerTransformerContract, diff --git a/contracts/zero-ex/src/migration.ts b/contracts/zero-ex/src/migration.ts index 8d211800e0..ef0af08b54 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,15 +37,15 @@ 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)) + (await OwnableFeatureContract.deployFrom0xArtifactAsync(artifacts.OwnableFeature, provider, txDefaults, artifacts)) .address, }; } @@ -107,32 +108,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 +151,7 @@ export async function fullMigrateAsync( txDefaults: Partial, features: Partial = {}, opts: Partial = {}, -): Promise { +): Promise { const migrator = await FullMigrationContract.deployFrom0xArtifactAsync( artifacts.FullMigration, provider, @@ -171,5 +172,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 69a5e1e1ff..86bac2f7a2 100644 --- a/contracts/zero-ex/src/wrappers.ts +++ b/contracts/zero-ex/src/wrappers.ts @@ -9,19 +9,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/log_metadata_transformer'; -export * from '../generated-wrappers/meta_transactions'; -export * from '../generated-wrappers/ownable'; +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 6f8b252f40..98540484e8 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -7,7 +7,7 @@ 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 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'; @@ -16,21 +16,22 @@ import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentran 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 IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json'; import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json'; 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 IUniswapV2Feature from '../test/generated-artifacts/IUniswapV2Feature.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'; @@ -54,11 +55,11 @@ import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTra import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json'; import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json'; -import * as MetaTransactions from '../test/generated-artifacts/MetaTransactions.json'; -import * as Ownable from '../test/generated-artifacts/Ownable.json'; +import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.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'; @@ -81,9 +82,9 @@ 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'; @@ -104,21 +105,22 @@ 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, + IUniswapV2Feature: IUniswapV2Feature 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, diff --git a/contracts/zero-ex/test/features/meta_transactions_test.ts b/contracts/zero-ex/test/features/meta_transactions_test.ts index c19f4a038c..c2b3ff20a0 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,8 +29,8 @@ 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; @@ -52,14 +52,14 @@ 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. diff --git a/contracts/zero-ex/test/features/ownable_test.ts b/contracts/zero-ex/test/features/ownable_test.ts index 428e6f0c9b..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); }); 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 dcb6592cde..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, diff --git a/contracts/zero-ex/test/features/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index a0a28e399b..55c5a65622 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -14,18 +14,18 @@ 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; @@ -39,8 +39,8 @@ blockchainTests.resets('TransformERC20 feature', env => { 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; @@ -51,7 +51,7 @@ blockchainTests.resets('TransformERC20 feature', env => { env.provider, env.txDefaults, { - transformERC20: (await TransformERC20Contract.deployFrom0xArtifactAsync( + transformERC20: (await TransformERC20FeatureContract.deployFrom0xArtifactAsync( artifacts.TestTransformERC20, env.provider, env.txDefaults, @@ -60,9 +60,9 @@ blockchainTests.resets('TransformERC20 feature', env => { }, { transformerDeployer }, ); - feature = new TransformERC20Contract(zeroEx.address, env.provider, { ...env.txDefaults, from: sender }, 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({ from: owner }); @@ -99,7 +99,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); @@ -122,7 +122,7 @@ 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); }); @@ -259,7 +259,7 @@ blockchainTests.resets('TransformERC20 feature', env => { outputToken: outputToken.address, }, ], - TransformERC20Events.TransformedERC20, + TransformERC20FeatureEvents.TransformedERC20, ); verifyEventsFromLogs( receipt.logs, @@ -316,7 +316,7 @@ blockchainTests.resets('TransformERC20 feature', env => { outputToken: ETH_TOKEN_ADDRESS, }, ], - TransformERC20Events.TransformedERC20, + TransformERC20FeatureEvents.TransformedERC20, ); verifyEventsFromLogs( receipt.logs, @@ -376,7 +376,7 @@ blockchainTests.resets('TransformERC20 feature', env => { outputToken: outputToken.address, }, ], - TransformERC20Events.TransformedERC20, + TransformERC20FeatureEvents.TransformedERC20, ); verifyEventsFromLogs( receipt.logs, 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/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 9b976b0c5f..aae0c1cf60 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -5,7 +5,7 @@ */ 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/fill_quote_transformer'; export * from '../test/generated-wrappers/fixin_common'; export * from '../test/generated-wrappers/fixin_e_i_p712'; @@ -14,20 +14,21 @@ 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_erc20_bridge'; export * from '../test/generated-wrappers/i_erc20_transformer'; 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_uniswap_v2_feature'; export * from '../test/generated-wrappers/i_zero_ex'; export * from '../test/generated-wrappers/initial_migration'; export * from '../test/generated-wrappers/lib_bootstrap'; @@ -52,11 +53,11 @@ export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors'; export * from '../test/generated-wrappers/lib_transform_erc20_storage'; export * from '../test/generated-wrappers/lib_wallet_rich_errors'; export * from '../test/generated-wrappers/log_metadata_transformer'; -export * from '../test/generated-wrappers/meta_transactions'; -export * from '../test/generated-wrappers/ownable'; +export * from '../test/generated-wrappers/meta_transactions_feature'; +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'; @@ -79,8 +80,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 123d1a4ff2..4fdb5af1db 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -9,25 +9,25 @@ "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/LogMetadataTransformer.json", - "generated-artifacts/MetaTransactions.json", - "generated-artifacts/Ownable.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/FillQuoteTransformer.json", "test/generated-artifacts/FixinCommon.json", "test/generated-artifacts/FixinEIP712.json", @@ -36,20 +36,21 @@ "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/IERC20Bridge.json", "test/generated-artifacts/IERC20Transformer.json", "test/generated-artifacts/IExchange.json", "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/IUniswapV2Feature.json", "test/generated-artifacts/IZeroEx.json", "test/generated-artifacts/InitialMigration.json", "test/generated-artifacts/LibBootstrap.json", @@ -74,11 +75,11 @@ "test/generated-artifacts/LibTransformERC20Storage.json", "test/generated-artifacts/LibWalletRichErrors.json", "test/generated-artifacts/LogMetadataTransformer.json", - "test/generated-artifacts/MetaTransactions.json", - "test/generated-artifacts/Ownable.json", + "test/generated-artifacts/MetaTransactionsFeature.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", @@ -101,8 +102,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", From 3cd03ed0f15039da617000681ae07658b088e6aa Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 20 Aug 2020 16:55:47 -0400 Subject: [PATCH 13/47] `@0x/migrations`: Update EP migration --- packages/migrations/CHANGELOG.json | 4 ++++ packages/migrations/src/migration.ts | 18 ++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index e2b39f9841..12fd9c63e2 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Refactor `migration.ts` a little", "pr": 2656 + }, + { + "note": "Update EP migration.", + "pr": "TODO" } ] }, diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 67af1f6f78..df0bf4c333 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -31,11 +31,9 @@ import { artifacts as exchangeProxyArtifacts, FillQuoteTransformerContract, fullMigrateAsync as fullMigrateExchangeProxyAsync, - ITokenSpenderContract, - ITransformERC20Contract, + IZeroExContract, PayTakerTransformerContract, WethTransformerContract, - ZeroExContract, } from '@0x/contracts-zero-ex'; import { Web3ProviderEngine, ZeroExProvider } from '@0x/subproviders'; import { BigNumber, providerUtils } from '@0x/utils'; @@ -400,7 +398,7 @@ async function _migrateExchangeProxyAsync( txDefaults: TxData, ): Promise< [ - ZeroExContract, + IZeroExContract, FillQuoteTransformerContract, PayTakerTransformerContract, WethTransformerContract, @@ -410,16 +408,8 @@ async function _migrateExchangeProxyAsync( ] > { 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( From b30f87f50cccfcf79cecfb469f6cb1b80772503e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 20 Aug 2020 16:56:40 -0400 Subject: [PATCH 14/47] `@0x/asset-swapper`: Use `IZeroExContract` in EP swap quote consumer. --- packages/asset-swapper/CHANGELOG.json | 4 ++++ .../exchange_proxy_swap_quote_consumer.ts | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 3943c1693f..a661f5b3ad 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -37,6 +37,10 @@ { "note": "Add `refundReceiver` to `ExchangeProxySwapQuoteConsumer` options.", "pr": 2657 + }, + { + "note": "Use `IZeroExContract` in EP swap quote consumer.", + "pr": "TODO" } ] }, 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 dc88874b58..b4d8f261eb 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 { assetDataUtils, encodeAffiliateFeeTransformerData, @@ -45,7 +45,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { affiliateFeeTransformer: number; }; - private readonly _transformFeature: ITransformERC20Contract; + private readonly _exchangeProxy: IZeroExContract; constructor( supportedProvider: SupportedProvider, @@ -58,7 +58,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, @@ -165,7 +165,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, @@ -183,7 +183,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { return { calldataHexString, ethAmount, - toAddress: this._transformFeature.address, + toAddress: this._exchangeProxy.address, allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget, }; } From 9cda9f69cdf3df1bb2ecbaa67fb20577db51f1fa Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 5 Aug 2020 13:33:07 -0400 Subject: [PATCH 15/47] `@0x/contracts-zero-ex`: Add reentrancy guard to mtx functions `@0x/contracts-zero-ex`: Add refund mechanism to mtxs `@0x/contracts-zero-ex`: Pass sender to transfomers. `@0x/contracts-zero-ex`: Refund protocol fees to `refundReceiver` in FQT. `@0x/utils`: Add EP flavor of `IllegalReentrancyError` `@0x/order-utils`: Add `refundReceiver` to FQT transform data. `@0x/asset-swapper`: Add `refundReceiver` support to EP swap quote consumer. --- contracts/zero-ex/CHANGELOG.json | 21 +++ .../src/errors/LibCommonRichErrors.sol | 6 +- .../src/features/MetaTransactions.sol | 43 +++-- .../src/features/SignatureValidator.sol | 49 +++-- .../contracts/src/features/TransformERC20.sol | 9 +- .../src/fixins/FixinReentrancyGuard.sol | 60 +++++++ .../src/storage/LibReentrancyGuardStorage.sol | 46 +++++ .../contracts/src/storage/LibStorage.sol | 3 +- .../transformers/AffiliateFeeTransformer.sol | 10 +- .../src/transformers/FillQuoteTransformer.sol | 37 +++- .../src/transformers/IERC20Transformer.sol | 23 ++- .../src/transformers/PayTakerTransformer.sol | 13 +- .../src/transformers/WethTransformer.sol | 12 +- .../test/TestFillQuoteTransformerHost.sol | 12 +- ...tMetaTransactionsTransformERC20Feature.sol | 44 +++++ .../test/TestMintTokenERC20Transformer.sol | 22 ++- .../contracts/test/TestTransformerBase.sol | 8 +- .../contracts/test/TestTransformerHost.sol | 8 +- .../test/TestWethTransformerHost.sol | 10 +- contracts/zero-ex/test/artifacts.ts | 2 + .../test/features/meta_transactions_test.ts | 168 +++++++++++++++++- .../test/features/transform_erc20_test.ts | 24 ++- .../affiliate_fee_transformer_test.ts | 21 ++- .../fill_quote_transformer_test.ts | 137 +++++++++++++- .../pay_taker_transformer_test.ts | 28 ++- contracts/zero-ex/test/wrappers.ts | 1 + contracts/zero-ex/tsconfig.json | 2 + packages/asset-swapper/CHANGELOG.json | 4 + packages/asset-swapper/src/index.ts | 1 + .../exchange_proxy_swap_quote_consumer.ts | 7 +- packages/asset-swapper/src/types.ts | 16 ++ packages/order-utils/CHANGELOG.json | 6 +- .../src/transformer_data_encoders.ts | 2 + packages/utils/CHANGELOG.json | 4 + .../zero-ex/common_revert_errors.ts | 17 +- 35 files changed, 747 insertions(+), 129 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/fixins/FixinReentrancyGuard.sol create mode 100644 contracts/zero-ex/contracts/src/storage/LibReentrancyGuardStorage.sol diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 69e75db6b6..ba26a2b504 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,25 @@ [ + { + "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 + } + ] + }, { "version": "0.2.0", "changes": [ 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/features/MetaTransactions.sol b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol index 975520a6a5..f9eca8960e 100644 --- a/contracts/zero-ex/contracts/src/features/MetaTransactions.sol +++ b/contracts/zero-ex/contracts/src/features/MetaTransactions.sol @@ -21,8 +21,10 @@ 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"; @@ -39,6 +41,7 @@ contract MetaTransactions is IFeature, IMetaTransactions, FixinCommon, + FixinReentrancyGuard, FixinEIP712 { using LibBytesV06 for bytes; @@ -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) { @@ -255,19 +272,9 @@ contract MetaTransactions is _validateMetaTransaction(state); // Mark the transaction executed. - assert(block.number > 0); 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( @@ -277,6 +284,16 @@ contract MetaTransactions is mtx.feeAmount ); } + + // 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(); + } emit MetaTransactionExecuted( state.hash, state.selector, @@ -367,7 +384,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 +411,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 { diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol index b7e6b1b71a..a8039af0e9 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol @@ -37,6 +37,13 @@ contract SignatureValidator is 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. @@ -160,12 +167,18 @@ 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 (v < 27) { + // Handle clients that encode v as 0 or 1. + v += 27; + } + 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 +192,21 @@ 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 (v < 27) { + // Handle clients that encode v as 0 or 1. + v += 27; + } + 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/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20.sol index e7bafaa355..a2fe5a48ff 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20.sol @@ -360,9 +360,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. 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/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..8ec1cd467e 100644 --- a/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/AffiliateFeeTransformer.sol @@ -58,18 +58,14 @@ contract AffiliateFeeTransformer is {} /// @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..646032dd4d 100644 --- a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol @@ -71,6 +71,12 @@ 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; } /// @dev Results of a call to `_fillOrder()`. @@ -108,6 +114,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 +142,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(); } @@ -248,6 +256,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; } diff --git a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol index 40fd9387c0..5b079ed686 100644 --- a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol @@ -25,17 +25,24 @@ 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. + 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/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/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/TestMetaTransactionsTransformERC20Feature.sol b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol index 913fa414d7..5c7d8f1596 100644 --- a/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/test/TestMetaTransactionsTransformERC20Feature.sol @@ -20,6 +20,7 @@ pragma solidity ^0.6.5; pragma experimental ABIEncoderV2; import "../src/features/TransformERC20.sol"; +import "../src/features/IMetaTransactions.sol"; contract TestMetaTransactionsTransformERC20Feature is @@ -48,6 +49,49 @@ contract TestMetaTransactionsTransformERC20Feature is revert('FAIL'); } + if (msg.value == 777) { + // Try to reenter `executeMetaTransaction()` + IMetaTransactions(address(this)).executeMetaTransaction( + IMetaTransactions.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()` + IMetaTransactions.MetaTransactionData[] memory mtxs = + new IMetaTransactions.MetaTransactionData[](1); + bytes[] memory signatures = new bytes[](1); + mtxs[0] = IMetaTransactions.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] = ""; + IMetaTransactions(address(this)).batchExecuteMetaTransactions( + mtxs, + signatures + ); + } + emit TransformERC20Called( msg.sender, msg.value, 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/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/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/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 70a5b4b55f..a71ff5ae2e 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -42,6 +42,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'; @@ -147,6 +148,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, diff --git a/contracts/zero-ex/test/features/meta_transactions_test.ts b/contracts/zero-ex/test/features/meta_transactions_test.ts index 943da28a5a..12e5f376c7 100644 --- a/contracts/zero-ex/test/features/meta_transactions_test.ts +++ b/contracts/zero-ex/test/features/meta_transactions_test.ts @@ -36,6 +36,10 @@ blockchainTests.resets('MetaTransactions feature', env => { 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(); @@ -263,7 +267,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, @@ -469,6 +473,72 @@ blockchainTests.resets('MetaTransactions feature', env => { ), ); }); + + it('cannot reenter `executeMetaTransaction()`', async () => { + const args = getRandomTransformERC20Args(); + const mtx = getRandomMetaTransaction({ + callData: transformERC20Feature + .transformERC20( + args.inputToken, + args.outputToken, + args.inputTokenAmount, + args.minOutputTokenAmount, + args.transformations, + ) + .getABIEncodedTransactionData(), + value: TRANSFORM_ERC20_REENTER_VALUE, + }); + const mtxHash = getExchangeProxyMetaTransactionHash(mtx); + const signature = await signMetaTransactionAsync(mtx); + const callOpts = { + gasPrice: mtx.maxGasPrice, + value: mtx.value, + }; + const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( + mtxHash, + undefined, + new ZeroExRevertErrors.Common.IllegalReentrancyError( + feature.getSelector('executeMetaTransaction'), + REENTRANCY_FLAG_MTX, + ).encode(), + ), + ); + }); + + it('cannot reenter `batchExecuteMetaTransactions()`', async () => { + const args = getRandomTransformERC20Args(); + const mtx = getRandomMetaTransaction({ + callData: transformERC20Feature + .transformERC20( + args.inputToken, + args.outputToken, + args.inputTokenAmount, + args.minOutputTokenAmount, + args.transformations, + ) + .getABIEncodedTransactionData(), + value: TRANSFORM_ERC20_BATCH_REENTER_VALUE, + }); + const mtxHash = getExchangeProxyMetaTransactionHash(mtx); + const signature = await signMetaTransactionAsync(mtx); + const callOpts = { + gasPrice: mtx.maxGasPrice, + value: mtx.value, + }; + const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( + mtxHash, + undefined, + new ZeroExRevertErrors.Common.IllegalReentrancyError( + feature.getSelector('batchExecuteMetaTransactions'), + REENTRANCY_FLAG_MTX, + ).encode(), + ), + ); + }); }); describe('batchExecuteMetaTransactions()', () => { @@ -526,6 +596,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/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 4625325a9c..a0a28e399b 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -37,6 +37,7 @@ 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; @@ -44,7 +45,7 @@ blockchainTests.resets('TransformERC20 feature', env => { let allowanceTarget: string; before(async () => { - [owner, taker, transformerDeployer] = await env.getAccountAddressesAsync(); + [owner, taker, sender, transformerDeployer] = await env.getAccountAddressesAsync(); zeroEx = await fullMigrateAsync( owner, env.provider, @@ -59,12 +60,12 @@ blockchainTests.resets('TransformERC20 feature', env => { }, { transformerDeployer }, ); - feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis); + feature = new TransformERC20Contract(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) .getAllowanceTarget() .callAsync(); - await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync(); + await feature.setQuoteSigner(callDataSigner).awaitTransactionSuccessAsync({ from: owner }); }); const { MAX_UINT256, ZERO_AMOUNT } = constants; @@ -73,7 +74,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); }); @@ -264,8 +265,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, + sender, taker, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -320,8 +322,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, taker, + sender, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -379,8 +382,9 @@ blockchainTests.resets('TransformERC20 feature', env => { receipt.logs, [ { - callDataHash: NULL_BYTES32, + sender, taker, + callDataHash: NULL_BYTES32, context: wallet.address, caller: zeroEx.address, data: transformation.data, @@ -496,8 +500,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 +510,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, 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..d6c2ffa38b 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, @@ -92,7 +94,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 +272,7 @@ blockchainTests.resets('FillQuoteTransformer', env => { signatures: [], maxOrderFillAmounts: [], fillAmount: MAX_UINT256, + refundReceiver: NULL_ADDRESS, ...fields, }); } @@ -313,6 +316,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -334,6 +339,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -358,6 +365,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -380,6 +389,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -404,6 +415,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -430,6 +443,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -462,6 +477,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -486,6 +503,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -511,6 +530,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, takerTokenBalance, + sender, + taker, encodeTransformData({ orders, signatures, @@ -535,6 +556,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, takerTokenBalance, + sender, + taker, encodeTransformData({ orders, signatures, @@ -564,6 +587,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -586,6 +611,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -608,6 +635,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -627,6 +656,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -640,6 +671,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 +757,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -675,6 +782,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -701,6 +810,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -725,6 +836,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -751,6 +864,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -774,6 +889,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -804,6 +921,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -828,6 +947,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -852,6 +973,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -873,6 +996,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -900,6 +1025,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -926,6 +1053,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -952,6 +1081,8 @@ blockchainTests.resets('FillQuoteTransformer', env => { transformer.address, takerToken.address, qfr.takerAssetSpent, + sender, + taker, encodeTransformData({ orders, signatures, @@ -980,6 +1111,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..4eba01669d 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -40,6 +40,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'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 0acd2f5537..a5f3b2bc0e 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -32,6 +32,7 @@ "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", @@ -62,6 +63,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", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index e69ee2d1dc..c1697f5304 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -85,6 +85,10 @@ { "note": "Enable Quote Report to be generated with an option `shouldGenerateQuoteReport`. Default is `false`", "pr": 2687 + }, + { + "note": "Add `refundReceiver` to `ExchangeProxySwapQuoteConsumer` options.", + "pr": 2657 } ] }, diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 64d4b32e4e..54e7c42306 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, 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..5fee5a2924 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 @@ -84,7 +84,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,6 +116,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { sellToken, buyToken: intermediateToken, side: FillQuoteTransformerSide.Sell, + refundReceiver: refundReceiver || NULL_ADDRESS, fillAmount: firstHopOrder.takerAssetAmount, maxOrderFillAmounts: [], orders: [firstHopOrder], @@ -125,8 +126,9 @@ 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: [], @@ -140,6 +142,7 @@ 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: [], 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/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index be2bacc702..290563e85a 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,10 @@ { "note": "Fix `decodeAffiliateFeeTransformerData`", "pr": 2658 + }, + { + "note": "Add `refundReceiver` field to `FillQuoteTransformer.TransformData`.", + "pr": 2657 } ] }, diff --git a/packages/order-utils/src/transformer_data_encoders.ts b/packages/order-utils/src/transformer_data_encoders.ts index cb3bb17d11..8f747bdbe7 100644 --- a/packages/order-utils/src/transformer_data_encoders.ts +++ b/packages/order-utils/src/transformer_data_encoders.ts @@ -37,6 +37,7 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([ { name: 'signatures', type: 'bytes[]' }, { name: 'maxOrderFillAmounts', type: 'uint256[]' }, { name: 'fillAmount', type: 'uint256' }, + { name: 'refundReceiver', type: 'address' }, ], }, ]); @@ -60,6 +61,7 @@ export interface FillQuoteTransformerData { signatures: string[]; maxOrderFillAmounts: BigNumber[]; fillAmount: BigNumber; + refundReceiver: string; } /** 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_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) { From 195d543ce3468981710c799ab941366f9955437a Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 6 Aug 2020 22:32:42 -0400 Subject: [PATCH 16/47] `@0x/contracts-zero-ex`: Remove bugged parity signature compatibility. --- .../zero-ex/contracts/src/features/SignatureValidator.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol index a8039af0e9..aeb0666844 100644 --- a/contracts/zero-ex/contracts/src/features/SignatureValidator.sol +++ b/contracts/zero-ex/contracts/src/features/SignatureValidator.sol @@ -167,10 +167,6 @@ contract SignatureValidator is uint8 v = uint8(signature[0]); bytes32 r = signature.readBytes32(1); bytes32 s = signature.readBytes32(33); - if (v < 27) { - // Handle clients that encode v as 0 or 1. - v += 27; - } if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) { recovered = ecrecover( hash, @@ -192,10 +188,6 @@ contract SignatureValidator is uint8 v = uint8(signature[0]); bytes32 r = signature.readBytes32(1); bytes32 s = signature.readBytes32(33); - if (v < 27) { - // Handle clients that encode v as 0 or 1. - v += 27; - } if (uint256(r) < ECDSA_SIGNATURE_R_LIMIT && uint256(s) < ECDSA_SIGNATURE_S_LIMIT) { recovered = ecrecover( keccak256(abi.encodePacked( From d93e72f56a2c1bc2517f73fbc4a573f80af1705d Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 6 Aug 2020 22:48:55 -0400 Subject: [PATCH 17/47] `@0x/asset-swapper`: Fix rebase conflicts --- packages/asset-swapper/src/constants.ts | 1 + 1 file changed, 1 insertion(+) 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; From b6319ba3d8f8586981f4082fe22d32d4457e5edc Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 10:35:02 -0400 Subject: [PATCH 18/47] `@0x/contracts-zero-ex`: Fix rebase artifacts. --- contracts/zero-ex/package.json | 4 ++-- contracts/zero-ex/src/artifacts.ts | 2 +- contracts/zero-ex/src/index.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 3a3ac463ee..a962fe51b7 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,LogMetadataTransformer", + "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer,SignatureValidator,MetaTransactions,LogMetadataTransformer,BridgeAdapter", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactions|IOwnable|ISignatureValidator|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactions|Ownable|PayTakerTransformer|SignatureValidator|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|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|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|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" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/src/artifacts.ts b/contracts/zero-ex/src/artifacts.ts index da2f5fecd2..2a71e7a2fd 100644 --- a/contracts/zero-ex/src/artifacts.ts +++ b/contracts/zero-ex/src/artifacts.ts @@ -50,6 +50,6 @@ export const artifacts = { AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact, SignatureValidator: SignatureValidator as ContractArtifact, MetaTransactions: MetaTransactions as ContractArtifact, - BridgeAdapter: BridgeAdapter 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..73819ecc9e 100644 --- a/contracts/zero-ex/src/index.ts +++ b/contracts/zero-ex/src/index.ts @@ -43,6 +43,7 @@ export { ITokenSpenderContract, ITransformERC20Contract, IZeroExContract, + LogMetadataTransformerContract, PayTakerTransformerContract, WethTransformerContract, ZeroExContract, From f9d02c9e27503816f57f64bf3b9ad54d9910d237 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:04:01 -0400 Subject: [PATCH 19/47] `@0x/utils`: Fix nested revert decoding --- packages/utils/src/revert_error.ts | 39 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 17 deletions(-) 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)) { From 1d6aef5cd64be557fec8689ff6d5543a0ad7469e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:05:14 -0400 Subject: [PATCH 20/47] `@0x/contracts-zero-ex`: Remove UniswapFeature `@0x/contracts-zero-ex`: Fix broken tests. --- .../src/features/IUniswapV2Feature.sol | 49 ------------------- contracts/zero-ex/package.json | 2 +- contracts/zero-ex/test/artifacts.ts | 8 ++- .../test/features/meta_transactions_test.ts | 4 +- .../zero-ex/test/features/ownable_test.ts | 2 +- .../test/features/token_spender_test.ts | 2 +- contracts/zero-ex/test/flash_wallet_test.ts | 4 +- contracts/zero-ex/test/wrappers.ts | 28 +++++------ contracts/zero-ex/tsconfig.json | 7 ++- 9 files changed, 27 insertions(+), 79 deletions(-) delete mode 100644 contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol diff --git a/contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol b/contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol deleted file mode 100644 index eaab9e3ed5..0000000000 --- a/contracts/zero-ex/contracts/src/features/IUniswapV2Feature.sol +++ /dev/null @@ -1,49 +0,0 @@ -/* - - 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/IERC20TokenV06.sol"; - - -/// @dev Feature for directly interacting with Uniswap V2 -interface IUniswapV2Feature { - - function sellToUniswapV2( - IERC20TokenV06 inputToken, - IERC20TokenV06 outputToken, - uint256 inputTokenAmount, - uint256 minOutputTokenAmount, - address recipient, - ) - external - payable - returns (uint256 outputTokenAmount); - - function buyFromUniswapV2( - IERC20TokenV06 inputToken, - IERC20TokenV06 outputToken, - uint256 maxInputTokenAmount, - uint256 outputTokenAmount, - address recipient, - ) - external - payable - returns (uint256 inputTokenAmount); -} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 9a35bc0407..354c0b044a 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -41,7 +41,7 @@ "config": { "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|BootstrapFeature|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinGasToken|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapV2Feature|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|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" + "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|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/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index b925ecfa0f..5224142cde 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -7,8 +7,8 @@ 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 BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.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'; @@ -16,8 +16,8 @@ import * as FixinReentrancyGuard from '../test/generated-artifacts/FixinReentran 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 IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.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'; import * as IExchange from '../test/generated-artifacts/IExchange.json'; @@ -32,7 +32,6 @@ import * as ISimpleFunctionRegistryFeature from '../test/generated-artifacts/ISi import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json'; import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json'; import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json'; -import * as IUniswapV2Feature from '../test/generated-artifacts/IUniswapV2Feature.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'; @@ -56,6 +55,7 @@ import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTra import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json'; import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json'; +import * as 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'; @@ -65,7 +65,6 @@ 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 MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json'; import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json'; import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json'; import * as SignatureValidatorFeature from '../test/generated-artifacts/SignatureValidatorFeature.json'; @@ -135,7 +134,6 @@ export const artifacts = { ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact, ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact, ITransformERC20Feature: ITransformERC20Feature as ContractArtifact, - IUniswapV2Feature: IUniswapV2Feature as ContractArtifact, MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact, OwnableFeature: OwnableFeature as ContractArtifact, SignatureValidatorFeature: SignatureValidatorFeature 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 f3633a551b..c6f84c3bcf 100644 --- a/contracts/zero-ex/test/features/meta_transactions_test.ts +++ b/contracts/zero-ex/test/features/meta_transactions_test.ts @@ -301,7 +301,7 @@ blockchainTests.resets('MetaTransactions feature', env => { new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError( mtxHash, actualCallData, - new StringRevertError('FAIL').toString(), + new StringRevertError('FAIL').encode(), ), ); }); @@ -469,7 +469,7 @@ blockchainTests.resets('MetaTransactions feature', env => { mtxHash, signers[0], signature, - ).toString(), + ).encode(), ), ); }); diff --git a/contracts/zero-ex/test/features/ownable_test.ts b/contracts/zero-ex/test/features/ownable_test.ts index 2809f98af0..8f3deb5c81 100644 --- a/contracts/zero-ex/test/features/ownable_test.ts +++ b/contracts/zero-ex/test/features/ownable_test.ts @@ -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/token_spender_test.ts b/contracts/zero-ex/test/features/token_spender_test.ts index dd4dfba51c..0a1d24b8b1 100644 --- a/contracts/zero-ex/test/features/token_spender_test.ts +++ b/contracts/zero-ex/test/features/token_spender_test.ts @@ -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/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/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index ae902ffa36..ee20838378 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -5,7 +5,7 @@ */ 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'; @@ -14,7 +14,7 @@ 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'; @@ -22,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'; @@ -53,7 +53,7 @@ export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors'; export * from '../test/generated-wrappers/lib_transform_erc20_storage'; export * from '../test/generated-wrappers/lib_wallet_rich_errors'; export * from '../test/generated-wrappers/log_metadata_transformer'; -export * from '../test/generated-wrappers/meta_transactions'; +export * from '../test/generated-wrappers/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'; @@ -63,10 +63,10 @@ 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'; @@ -89,8 +89,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/tsconfig.json b/contracts/zero-ex/tsconfig.json index f7b6cc3179..b99efc9771 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -28,8 +28,8 @@ "generated-artifacts/ZeroEx.json", "test/generated-artifacts/AffiliateFeeTransformer.json", "test/generated-artifacts/AllowanceTarget.json", - "test/generated-artifacts/BridgeAdapter.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", @@ -37,6 +37,7 @@ "test/generated-artifacts/FlashWallet.json", "test/generated-artifacts/FullMigration.json", "test/generated-artifacts/IAllowanceTarget.json", + "test/generated-artifacts/IBootstrapFeature.json", "test/generated-artifacts/IBridgeAdapter.json", "test/generated-artifacts/IERC20Bridge.json", "test/generated-artifacts/IERC20Transformer.json", @@ -51,7 +52,6 @@ "test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json", "test/generated-artifacts/ITokenSpenderFeature.json", "test/generated-artifacts/ITransformERC20Feature.json", - "test/generated-artifacts/IUniswapV2Feature.json", "test/generated-artifacts/IZeroEx.json", "test/generated-artifacts/InitialMigration.json", "test/generated-artifacts/LibBootstrap.json", @@ -76,6 +76,7 @@ "test/generated-artifacts/LibTransformERC20Storage.json", "test/generated-artifacts/LibWalletRichErrors.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", @@ -85,8 +86,6 @@ "test/generated-artifacts/MixinUniswap.json", "test/generated-artifacts/MixinUniswapV2.json", "test/generated-artifacts/MixinZeroExBridge.json", - "test/generated-artifacts/Ownable.json", - "test/generated-artifacts/MetaTransactionsFeature.json", "test/generated-artifacts/OwnableFeature.json", "test/generated-artifacts/PayTakerTransformer.json", "test/generated-artifacts/SignatureValidatorFeature.json", From 79254de7bce28f1064eb6e13520970df06855bdd Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:53:49 -0400 Subject: [PATCH 21/47] `@0x/contracts-exchange`: Fix failing tests. --- contracts/exchange/test/dispatcher.ts | 4 ++-- contracts/exchange/test/signature_validator.ts | 10 +++++----- .../exchange/test/transactions_unit_tests.ts | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) 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) From 78dfb6d5252b0421d55753ab8e0c826ffa0dadcd Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:54:17 -0400 Subject: [PATCH 22/47] `@0x/migrations`: Fix linter error. --- packages/migrations/src/migration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index e885e26770..1400d8a89a 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -31,7 +31,6 @@ import { BridgeAdapterContract, FillQuoteTransformerContract, fullMigrateAsync as fullMigrateExchangeProxyAsync, - IZeroExContract, PayTakerTransformerContract, WethTransformerContract, } from '@0x/contracts-zero-ex'; From 36ad373f03f623b463a689ea50f6c72c2534a3b2 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:55:04 -0400 Subject: [PATCH 23/47] `@0x/utils`: Fix `StakingErrors.ExchangeManagerError` incorrect param name --- .../utils/src/revert_errors/staking/staking_revert_errors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, }); } From 1cdc6e7184c88a84b06548fab2983c1cc32c3802 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 16:56:32 -0400 Subject: [PATCH 24/47] `@0x/contracts-integrations`: Remove stray `only` modifier. --- contracts/integrations/test/exchange-proxy/mtx_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/integrations/test/exchange-proxy/mtx_test.ts b/contracts/integrations/test/exchange-proxy/mtx_test.ts index d62037945c..7a78c8a952 100644 --- a/contracts/integrations/test/exchange-proxy/mtx_test.ts +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -25,7 +25,7 @@ import * as ethjs from 'ethereumjs-util'; const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, NULL_BYTES32, ZERO_AMOUNT } = constants; -blockchainTests.resets.only('exchange proxy - meta-transactions', env => { +blockchainTests.resets('exchange proxy - meta-transactions', env => { const quoteSignerKey = hexUtils.random(); const quoteSigner = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(quoteSignerKey))); let owner: string; From d8844f6970bd6e728a40987f1a611b5f42ffd3ea Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 17:32:29 -0400 Subject: [PATCH 25/47] `@0x/contracts-integrations`: Fix broken tests. --- contracts/integrations/test/exchange/core_test.ts | 12 ++++++------ .../test/exchange/fill_dydx_order_test.ts | 2 +- .../test/exchange/transaction_protocol_fee_test.ts | 4 ++-- .../integrations/test/exchange/transaction_test.ts | 10 +++++----- .../test/stop-limit/chainlink_stop_limit_test.ts | 4 ++-- contracts/zero-ex/src/migration.ts | 8 ++++++-- .../zero-ex/test/features/meta_transactions_test.ts | 7 ++++++- .../zero-ex/test/features/transform_erc20_test.ts | 13 +++++++++++-- 8 files changed, 39 insertions(+), 21 deletions(-) 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/src/migration.ts b/contracts/zero-ex/src/migration.ts index ef0af08b54..6277b8931d 100644 --- a/contracts/zero-ex/src/migration.ts +++ b/contracts/zero-ex/src/migration.ts @@ -45,8 +45,12 @@ export async function deployBootstrapFeaturesAsync( )).address, ownable: features.ownable || - (await OwnableFeatureContract.deployFrom0xArtifactAsync(artifacts.OwnableFeature, provider, txDefaults, artifacts)) - .address, + (await OwnableFeatureContract.deployFrom0xArtifactAsync( + artifacts.OwnableFeature, + provider, + txDefaults, + artifacts, + )).address, }; } diff --git a/contracts/zero-ex/test/features/meta_transactions_test.ts b/contracts/zero-ex/test/features/meta_transactions_test.ts index c6f84c3bcf..8e08b02d45 100644 --- a/contracts/zero-ex/test/features/meta_transactions_test.ts +++ b/contracts/zero-ex/test/features/meta_transactions_test.ts @@ -52,7 +52,12 @@ blockchainTests.resets('MetaTransactions feature', env => { zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, { transformERC20: transformERC20Feature.address, }); - feature = new MetaTransactionsFeatureContract(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, diff --git a/contracts/zero-ex/test/features/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 55c5a65622..834e1519b8 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -60,7 +60,12 @@ blockchainTests.resets('TransformERC20 feature', env => { }, { transformerDeployer }, ); - feature = new TransformERC20FeatureContract(zeroEx.address, env.provider, { ...env.txDefaults, from: sender }, 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 ITokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults) .getAllowanceTarget() @@ -122,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 }], TransformERC20FeatureEvents.QuoteSignerUpdated); + verifyEventsFromLogs( + receipt.logs, + [{ quoteSigner: newSigner }], + TransformERC20FeatureEvents.QuoteSignerUpdated, + ); const actualSigner = await feature.getQuoteSigner().callAsync(); expect(actualSigner).to.eq(newSigner); }); From e9c91e59bd782564a9fe95a67451ecc0ddfd28b8 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 17:51:53 -0400 Subject: [PATCH 26/47] `@0x/contracts-zero-ex`: Fix shadowed variable in `TransformerDeployer`. --- .../zero-ex/contracts/src/external/TransformerDeployer.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol b/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol index 6e369e523d..5ef07219b2 100644 --- a/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol +++ b/contracts/zero-ex/contracts/src/external/TransformerDeployer.sol @@ -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]); } } From 4b9867f16783d6ef6aed9c7c26eb4987af571b2e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 18:02:17 -0400 Subject: [PATCH 27/47] update changelogs --- contracts/zero-ex/CHANGELOG.json | 6 +++--- packages/asset-swapper/CHANGELOG.json | 2 +- packages/migrations/CHANGELOG.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index f38fbe5ac2..7285977115 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -32,15 +32,15 @@ }, { "note": "Add `IUniswapV2Feature`", - "pr": "TODO" + "pr": 2657 }, { "note": "Rename all feature contracts to have `Feature` suffix", - "pr": "TODO" + "pr": 2657 }, { "note": "Return `IZeroExContract` in `fullMigrateAsync()`", - "pr": "TODO" + "pr": 2657 } ] }, diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 85b3890512..085a7b21fe 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -92,7 +92,7 @@ }, { "note": "Use `IZeroExContract` in EP swap quote consumer.", - "pr": "TODO" + "pr": 2657 } ] }, diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index f8aaafb220..64c9ef9ea7 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -16,7 +16,7 @@ }, { "note": "Update EP migration.", - "pr": "TODO" + "pr": 2657 } ] }, From 43810835d711ebea36ef3c03d081d596a3fa4df0 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 18:52:32 -0400 Subject: [PATCH 28/47] `@0x/utils`: Fix broken test --- packages/utils/test/revert_error_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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', () => { From 889b58a91480cb39391eb89b2295be0746c49263 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 1 Sep 2020 22:11:05 -0400 Subject: [PATCH 29/47] `@0x/contracts-zero-ex`: Add RFQT taker enforcement to FQT. `@0x/contracts-zero-ex`: Remove redundant event from `BridgeAdapter`. `@0x/contracts-zero-ex`: Treat all calldata as signed if quote signer is not in `TransformERC20Feature`. `@0x/contracts-zero-ex`: Update bridge adapters --- .../test/exchange-proxy/mtx_test.ts | 80 ++++++++++++- contracts/zero-ex/CHANGELOG.json | 16 ++- .../src/features/TransformERC20Feature.sol | 8 +- .../src/transformers/FillQuoteTransformer.sol | 20 +++- .../src/transformers/IERC20Transformer.sol | 1 + .../bridges/BridgeAdapter.sol | 47 +++++--- .../bridges/IBridgeAdapter.sol | 0 .../bridges/mixins/MixinAdapterAddresses.sol | 2 + .../bridges/mixins/MixinBalancer.sol | 28 ++--- .../bridges/mixins/MixinCurve.sol | 11 +- .../bridges/mixins/MixinKyber.sol | 46 ++++---- .../bridges/mixins/MixinMStable.sol | 25 ++-- .../bridges/mixins/MixinMooniswap.sol | 107 ++++++++++++++++++ .../bridges/mixins/MixinOasis.sol | 37 +++--- .../bridges/mixins/MixinUniswap.sol | 60 +++++----- .../bridges/mixins/MixinUniswapV2.sol | 15 +-- .../bridges/mixins/MixinZeroExBridge.sol | 21 ++-- contracts/zero-ex/package.json | 2 +- contracts/zero-ex/test/artifacts.ts | 24 ++-- .../test/features/transform_erc20_test.ts | 35 +++++- .../fill_quote_transformer_test.ts | 3 + contracts/zero-ex/test/wrappers.ts | 1 + contracts/zero-ex/tsconfig.json | 1 + 23 files changed, 423 insertions(+), 167 deletions(-) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/BridgeAdapter.sol (82%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/IBridgeAdapter.sol (100%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinAdapterAddresses.sol (94%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinBalancer.sol (74%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinCurve.sol (84%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinKyber.sol (76%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinMStable.sol (73%) create mode 100644 contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinOasis.sol (63%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinUniswap.sol (79%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinUniswapV2.sol (90%) rename contracts/zero-ex/contracts/src/{ => transformers}/bridges/mixins/MixinZeroExBridge.sol (77%) diff --git a/contracts/integrations/test/exchange-proxy/mtx_test.ts b/contracts/integrations/test/exchange-proxy/mtx_test.ts index 7a78c8a952..8b7a9982d3 100644 --- a/contracts/integrations/test/exchange-proxy/mtx_test.ts +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -106,7 +106,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { orders: SignedOrder[]; } - async function generateSwapAsync(orderFields: Partial = {}): Promise { + async function generateSwapAsync(orderFields: Partial = {}, isRfqt: boolean = false): Promise { const order = await signatureUtils.ecSignTypedDataOrderAsync( env.provider, { @@ -116,7 +116,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { salt: new BigNumber(hexUtils.random()), feeRecipientAddress: NULL_ADDRESS, senderAddress: NULL_ADDRESS, - takerAddress: flashWalletAddress, + takerAddress: isRfqt ? flashWalletAddress : NULL_ADDRESS, makerAddress: maker, makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address), takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address), @@ -144,6 +144,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { fillAmount: order.takerAssetAmount, maxOrderFillAmounts: [], refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender. + rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS, side: FillQuoteTransformerSide.Sell, }), }, @@ -205,6 +206,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { async function createMetaTransactionAsync( data: string, value: BigNumber, + fee?: BigNumber | number, ): Promise { return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync( env.provider, @@ -218,7 +220,7 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { salt: new BigNumber(hexUtils.random()), callData: data, feeToken: feeToken.address, - feeAmount: getRandomPortion(TAKER_FEE_BALANCE), + feeAmount: fee !== undefined ? new BigNumber(fee) : getRandomPortion(TAKER_FEE_BALANCE), domain: { chainId: 1, name: 'ZeroEx', @@ -230,6 +232,42 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { ); } + 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)); @@ -301,4 +339,40 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { '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', + ); + }); }); diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 7285977115..65018dc553 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -30,10 +30,6 @@ "note": "Add `LogMetadataTransformer`", "pr": 2657 }, - { - "note": "Add `IUniswapV2Feature`", - "pr": 2657 - }, { "note": "Rename all feature contracts to have `Feature` suffix", "pr": 2657 @@ -41,6 +37,18 @@ { "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 } ] }, diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol index f203a59d3e..d09f597962 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol @@ -386,13 +386,19 @@ contract TransformERC20Feature 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 (ISignatureValidatorFeature(address(this)).isValidHashSignature( callDataHash, - getQuoteSigner(), + quoteSigner, signature )) { return callDataHash; diff --git a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol index 646032dd4d..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. @@ -77,6 +77,9 @@ contract FillQuoteTransformer is // `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()`. @@ -96,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 @@ -178,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) { @@ -434,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 5b079ed686..928c432745 100644 --- a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol @@ -28,6 +28,7 @@ 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; 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 94% 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..e83ceebf41 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; @@ -36,6 +37,7 @@ contract MixinAdapterAddresses address uniswapV2Router; address uniswapExchangeFactory; address mStable; + address mooniswapRegistry; // Other address weth; } 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..e9d450b9ec --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol @@ -0,0 +1,107 @@ + +/* + + 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 Moooniswap registry interface. +interface IMooniswapRegistry { + + function pools( + IERC20TokenV06 token1, + IERC20TokenV06 token2 + ) + external + view + returns (IMooniswapPool); +} + +/// @dev BridgeAdapter mixin for mooniswap. +contract MixinMooniswap is + MixinAdapterAddresses +{ + using LibERC20TokenV06 for IERC20TokenV06; + using LibERC20TokenV06 for IEtherTokenV06; + + /// @dev Mooniswap registry contract. + IMooniswapRegistry private immutable REGISTRY; + /// @dev WETH token. + IEtherTokenV06 private immutable WETH; + + constructor(AdapterAddresses memory addresses) + public + { + REGISTRY = IMooniswapRegistry(addresses.mooniswapRegistry); + WETH = IEtherTokenV06(addresses.weth); + } + + function _tradeMooniswap( + IERC20TokenV06 buyToken, + uint256 sellAmount, + bytes memory bridgeData + ) + internal + returns (uint256 boughtAmount) + { + IERC20TokenV06 sellToken = abi.decode(bridgeData, (IERC20TokenV06)); + IMooniswapPool pool = REGISTRY.pools(sellToken, buyToken); + + // 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) + ); + } +} 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/package.json b/contracts/zero-ex/package.json index 354c0b044a..528789cde1 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -41,7 +41,7 @@ "config": { "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|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|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" + "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/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 5224142cde..b0e690901d 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -60,6 +60,7 @@ import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapter 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'; @@ -100,17 +101,6 @@ 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, @@ -164,6 +154,18 @@ export const artifacts = { 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/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 834e1519b8..8db631a22b 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -28,11 +28,11 @@ import { TransformERC20FeatureEvents, } from '../wrappers'; -const { NULL_BYTES, NULL_BYTES32 } = constants; +const { NULL_ADDRESS, NULL_BYTES, NULL_BYTES32 } = constants; type MintTokenTransformerEvent = DecodedLogEntry; -blockchainTests.resets('TransformERC20 feature', env => { +blockchainTests.resets.only('TransformERC20 feature', env => { const callDataSignerKey = hexUtils.random(); const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey))); let owner: string; @@ -687,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/transformers/fill_quote_transformer_test.ts b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts index d6c2ffa38b..c7c159af4a 100644 --- a/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts @@ -62,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, @@ -71,6 +72,7 @@ blockchainTests.resets('FillQuoteTransformer', env => { uniswapV2Router: NULL_ADDRESS, uniswapExchangeFactory: NULL_ADDRESS, mStable: NULL_ADDRESS, + mooniswapRegistry: NULL_ADDRESS, weth: NULL_ADDRESS, }, ); @@ -273,6 +275,7 @@ blockchainTests.resets('FillQuoteTransformer', env => { maxOrderFillAmounts: [], fillAmount: MAX_UINT256, refundReceiver: NULL_ADDRESS, + rfqtTakerAddress: NULL_ADDRESS, ...fields, }); } diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index ee20838378..7b775aa0f6 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -59,6 +59,7 @@ 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'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index b99efc9771..9d53f0d9c0 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -82,6 +82,7 @@ "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", From 52d36f5a8b475b85b09b9b1593ff902e5cdf07ea Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 11:54:32 -0400 Subject: [PATCH 30/47] `@0x/migrations`: Fix FQT deployment --- packages/migrations/CHANGELOG.json | 4 ++++ packages/migrations/src/migration.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 64c9ef9ea7..bc08fc0844 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -17,6 +17,10 @@ { "note": "Update EP migration.", "pr": 2657 + }, + { + "note": "Add mooniswap addresses to `BridgeAdapter` deployment", + "pr": 2692 } ] }, diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 1400d8a89a..56285a5f4d 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -314,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, @@ -323,6 +324,7 @@ export async function runMigrationsAsync( uniswapV2Router: NULL_ADDRESS, uniswapExchangeFactory: NULL_ADDRESS, mStable: NULL_ADDRESS, + mooniswapRegistry: NULL_ADDRESS, weth: etherToken.address, }, ); From dba69722815fb799ff2c5eba23017a1727663ce3 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 12:43:10 -0400 Subject: [PATCH 31/47] `@0x/order-utils`: Add `rfqtTakerAddress` to `FillQuoteTransformerData` --- packages/order-utils/CHANGELOG.json | 4 ++++ packages/order-utils/src/transformer_utils.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index f1dd56d75f..fdee878b97 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -21,6 +21,10 @@ { "note": "Fix EP signature utils schema assertion.", "pr": 2657 + }, + { + "note": "Add `rfqtTakerAddress` to `FillQuoteTransformerData`", + "pr": 2692 } ] }, diff --git a/packages/order-utils/src/transformer_utils.ts b/packages/order-utils/src/transformer_utils.ts index 1383558cb5..9bdd93b67b 100644 --- a/packages/order-utils/src/transformer_utils.ts +++ b/packages/order-utils/src/transformer_utils.ts @@ -43,6 +43,7 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([ { name: 'maxOrderFillAmounts', type: 'uint256[]' }, { name: 'fillAmount', type: 'uint256' }, { name: 'refundReceiver', type: 'address' }, + { name: 'rfqtTakerAddress', type: 'address' }, ], }, ]); @@ -67,6 +68,7 @@ export interface FillQuoteTransformerData { maxOrderFillAmounts: BigNumber[]; fillAmount: BigNumber; refundReceiver: string; + rfqtTakerAddress: string; } /** From a6cf8ae0b66d0355499c508024244470549710b2 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 12:43:29 -0400 Subject: [PATCH 32/47] `@0x/asset-swapper`: Add `rfqtTakerAddress` to `FillQuoteTransformerData` --- packages/asset-swapper/CHANGELOG.json | 4 ++++ .../src/quote_consumers/exchange_proxy_swap_quote_consumer.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 085a7b21fe..f42fd76680 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -93,6 +93,10 @@ { "note": "Use `IZeroExContract` in EP swap quote consumer.", "pr": 2657 + }, + { + "note": "Set `rfqtTakerAddress` to null in EP consumer", + "pr": 2692 } ] }, 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 d69f31b160..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 @@ -118,6 +118,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { refundReceiver: refundReceiver || NULL_ADDRESS, fillAmount: firstHopOrder.takerAssetAmount, maxOrderFillAmounts: [], + rfqtTakerAddress: NULL_ADDRESS, orders: [firstHopOrder], signatures: [firstHopOrder.signature], }), @@ -131,6 +132,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { side: FillQuoteTransformerSide.Sell, fillAmount: MAX_UINT256, maxOrderFillAmounts: [], + rfqtTakerAddress: NULL_ADDRESS, orders: [secondHopOrder], signatures: [secondHopOrder.signature], }), @@ -145,6 +147,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { 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), }), From 705f46717f2a8f411f890fa9498e4fc825d5aa28 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 15:56:24 -0400 Subject: [PATCH 33/47] `@0x/contracts-integrations`: Add EP + MTX + RFQT integration tests. --- contracts/integrations/CHANGELOG.json | 4 ++ .../test/exchange-proxy/mtx_test.ts | 50 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) 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/test/exchange-proxy/mtx_test.ts b/contracts/integrations/test/exchange-proxy/mtx_test.ts index 8b7a9982d3..c1844e84b3 100644 --- a/contracts/integrations/test/exchange-proxy/mtx_test.ts +++ b/contracts/integrations/test/exchange-proxy/mtx_test.ts @@ -20,7 +20,7 @@ import { SignedExchangeProxyMetaTransaction, } from '@0x/order-utils'; import { AssetProxyId, Order, SignedOrder } from '@0x/types'; -import { BigNumber, hexUtils } from '@0x/utils'; +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; @@ -375,4 +375,52 @@ blockchainTests.resets('exchange proxy - meta-transactions', env => { '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()); + }); }); From 4049143630c8a1920a9e1c9e1a7816d3d5d1ef8b Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 17:42:03 -0400 Subject: [PATCH 34/47] `@0x/asset-swapper`: Return Mooniswap pool in sampler and encode it in bridge data --- packages/asset-swapper/CHANGELOG.json | 4 + .../contracts/src/MooniswapSampler.sol | 106 ++++++++++++------ .../utils/market_operation_utils/orders.ts | 14 +++ .../sampler_operations.ts | 17 +++ .../src/utils/market_operation_utils/types.ts | 4 + 5 files changed, 110 insertions(+), 35 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index f42fd76680..d09eb21524 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -97,6 +97,10 @@ { "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..0c8d8f6a28 100644 --- a/packages/asset-swapper/contracts/src/MooniswapSampler.sol +++ b/packages/asset-swapper/contracts/src/MooniswapSampler.sol @@ -46,44 +46,31 @@ 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; + address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken; + address mooniswapMakerToken = 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) + pool = IMooniswap( + IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) ); // If there is no pool then return early if (address(pool) == address(0)) { - return makerTokenAmounts; + return (pool, makerTokenAmounts); } - uint256 poolBalance = _takerToken == address(0) ? address(pool).balance : IERC20Token(_takerToken).balanceOf(address(pool)); - 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( + pool, + mooniswapTakerToken, + mooniswapMakerToken, + takerTokenAmounts[i] + ); // Exit early if the amount is too high for the source to serve if (buyAmount == 0) { break; @@ -92,6 +79,38 @@ contract MooniswapSampler is } } + function sampleSingleSellFromMooniswapPool( + IMooniswap pool, + address mooniswapTakerToken, + address mooniswapMakerToken, + uint256 takerTokenAmount + ) + public + view + returns (uint256 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( + IMooniswap(0).getReturn.selector, + mooniswapTakerToken, + mooniswapMakerToken, + takerTokenAmount + )); + if (didSucceed) { + makerTokenAmount = abi.decode(resultData, (uint256)); + } + } + /// @dev Sample buy quotes from Mooniswap. /// @param takerToken Address of the taker token (what to sell). /// @param makerToken Address of the maker token (what to buy). @@ -105,12 +124,28 @@ contract MooniswapSampler is ) public view - returns (uint256[] memory takerTokenAmounts) + returns (IMooniswap pool, uint256[] memory takerTokenAmounts) { - return _sampleApproximateBuys( + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken; + address mooniswapMakerToken = makerToken == _getWethAddress() ? address(0) : makerToken; + // Find the pool for the pair, ETH is represented + // as address(0) + pool = IMooniswap( + IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) + ); + // If there is no pool then return early + if (address(pool) == address(0)) { + return (pool, takerTokenAmounts); + } + + takerTokenAmounts = _sampleApproximateBuys( ApproximateBuyQuoteOpts({ - makerTokenData: abi.encode(makerToken), - takerTokenData: abi.encode(takerToken), + makerTokenData: abi.encode(mooniswapMakerToken, pool), + takerTokenData: abi.encode(mooniswapTakerToken, pool), getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap }), makerTokenAmounts @@ -126,15 +161,16 @@ contract MooniswapSampler is view returns (uint256 buyAmount) { - (address takerToken) = - abi.decode(takerTokenData, (address)); - (address makerToken) = + (address mooniswapTakerToken, IMooniswap pool) = + abi.decode(takerTokenData, (address, IMooniswap)); + (address mooniswapMakerToken) = abi.decode(makerTokenData, (address)); (bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector( - this.sampleSellsFromMooniswap.selector, - takerToken, - makerToken, + this.sampleSingleSellFromMooniswapPool.selector, + pool, + mooniswapTakerToken, + mooniswapMakerToken, _toSingleValueArray(sellAmount) )); if (!success) { 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..2447baf9a6 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,11 @@ 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: 'pool', 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; From f41e13b574017f9df6e92f27f9408e128d91d704 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 17:45:14 -0400 Subject: [PATCH 35/47] `@0x/contracts-zero-ex`: Decode secret mooniswap bridge data parameter. --- .../src/transformers/bridges/mixins/MixinMooniswap.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol index e9d450b9ec..6f733b8439 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol @@ -80,8 +80,8 @@ contract MixinMooniswap is internal returns (uint256 boughtAmount) { - IERC20TokenV06 sellToken = abi.decode(bridgeData, (IERC20TokenV06)); - IMooniswapPool pool = REGISTRY.pools(sellToken, buyToken); + (IERC20TokenV06 sellToken, IMooniswapPool pool) = + abi.decode(bridgeData, (IERC20TokenV06, IMooniswapPool)); // Convert WETH to ETH. uint256 ethValue = 0; From 9f7840e12b1d21103e889eaee82070a61e973eff Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 21:22:07 -0400 Subject: [PATCH 36/47] `@0x/contracts-zero-ex`: Remove `only` from tests. --- contracts/zero-ex/test/features/transform_erc20_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/zero-ex/test/features/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts index 8db631a22b..77e44a3a6b 100644 --- a/contracts/zero-ex/test/features/transform_erc20_test.ts +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -32,7 +32,7 @@ const { NULL_ADDRESS, NULL_BYTES, NULL_BYTES32 } = constants; type MintTokenTransformerEvent = DecodedLogEntry; -blockchainTests.resets.only('TransformERC20 feature', env => { +blockchainTests.resets('TransformERC20 feature', env => { const callDataSignerKey = hexUtils.random(); const callDataSigner = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(callDataSignerKey))); let owner: string; From 3bb60fee192ff1d41598f20219b8a5722923870b Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 21:22:24 -0400 Subject: [PATCH 37/47] `@0x/asset-swapper`: Export `MooniswapFillData`. --- packages/asset-swapper/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 54e7c42306..fbf49b2185 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -140,6 +140,7 @@ export { LiquidityProviderFillData, MarketDepth, MarketDepthSide, + MooniswapFillData, MultiBridgeFillData, MultiHopFillData, NativeCollapsedFill, From 7e53b4f834ddfa075a45e2e03d04e7c4e943d193 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 2 Sep 2020 22:16:53 -0400 Subject: [PATCH 38/47] `@0x/contract-addresses`: Update transformer deployer and transformers for champagne-problems deployment. `@0x/migrations`: Reorder transformer deployments. --- packages/contract-addresses/CHANGELOG.json | 4 ++ packages/contract-addresses/addresses.json | 46 +++++++++++----------- packages/migrations/CHANGELOG.json | 4 ++ packages/migrations/src/migration.ts | 18 ++++----- 4 files changed, 40 insertions(+), 32 deletions(-) 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..8ef15002c0 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": "0x81c0ab53a7352d2e97f682a37cba44e54647eefb", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3", "mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0xa8c8cf29699f223766f47fe79e2b7eb1a90e08c8", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x9fbacfe5cf2ee08a3f7c71c638603b6d5961bfeb", + "payTakerTransformer": "0xc9406e3ec8c4005951e0d9430a2981ca720d76c9", + "affiliateFeeTransformer": "0x2cfdacce1dcf97950a7dd0adad278a1077fa4224", + "fillQuoteTransformer": "0x3ae59e53ea6a2e364ba68e584c8db95134a0021a" } }, "3": { @@ -88,15 +88,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x58faf8e7bad3131d00d4deacb7488744818b9570", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", + "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", + "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", + "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" } }, "4": { @@ -138,15 +138,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x2ebe42dab6c6dec58ba494d111d1464836216c2a", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", + "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", + "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", + "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" } }, "42": { @@ -188,15 +188,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x80a36559ab9a497fb658325ed771a584eb0f13da", + "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x7bab5f7299e1ca123bb44eb71e6c89be7e558cc8", - "payTakerTransformer": "0xe8c07a119452b55eee2f999478aab97f3656d841", - "fillQuoteTransformer": "0x10c394406d2b15fb8e67b9a7a0dd03fa4d3e8099", - "affiliateFeeTransformer": "0x9d7174f55b50dad2e417bd567ad2da1ae4eef76d" + "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", + "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", + "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e", } }, "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/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index bc08fc0844..25a4266ec0 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -21,6 +21,10 @@ { "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 56285a5f4d..1589defc31 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -334,13 +334,12 @@ export async function runMigrationsAsync( 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, @@ -348,18 +347,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 = { From ab28e42c22f59eedfa563bd0a3022cd5fee1ab14 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 01:07:00 -0400 Subject: [PATCH 39/47] `@0x/asset-swapper`: Fix mooniswap encoding --- .../asset-swapper/src/utils/market_operation_utils/orders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2447baf9a6..b12af98f6a 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -417,7 +417,7 @@ function createKyberBridgeData(fromTokenAddress: string, hint: string): string { } function createMooniswapBridgeData(takerToken: string, poolAddress: string): string { - const encoder = AbiEncoder.create([{ name: 'takerToken', type: 'address' }, { name: 'pool', type: 'address' }]); + const encoder = AbiEncoder.create([{ name: 'takerToken', type: 'address' }, { name: 'poolAddress', type: 'address' }]); return encoder.encode({ takerToken, poolAddress }); } From 3733d503dbc801c2552bbf9253e80d3c37df1cb6 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 01:21:09 -0400 Subject: [PATCH 40/47] `@0x/contracts-zero-ex`: Remove `mooniswapRegistry` from `BridgeAdapter` addreses --- .../bridges/mixins/MixinAdapterAddresses.sol | 1 - .../bridges/mixins/MixinMooniswap.sol | 15 --------------- .../transformers/fill_quote_transformer_test.ts | 1 - 3 files changed, 17 deletions(-) diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol index e83ceebf41..7a08ad2973 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinAdapterAddresses.sol @@ -37,7 +37,6 @@ contract MixinAdapterAddresses address uniswapV2Router; address uniswapExchangeFactory; address mStable; - address mooniswapRegistry; // Other address weth; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol index 6f733b8439..c58f41b137 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol @@ -41,18 +41,6 @@ interface IMooniswapPool { returns (uint256 boughtAmount); } -/// @dev Moooniswap registry interface. -interface IMooniswapRegistry { - - function pools( - IERC20TokenV06 token1, - IERC20TokenV06 token2 - ) - external - view - returns (IMooniswapPool); -} - /// @dev BridgeAdapter mixin for mooniswap. contract MixinMooniswap is MixinAdapterAddresses @@ -60,15 +48,12 @@ contract MixinMooniswap is using LibERC20TokenV06 for IERC20TokenV06; using LibERC20TokenV06 for IEtherTokenV06; - /// @dev Mooniswap registry contract. - IMooniswapRegistry private immutable REGISTRY; /// @dev WETH token. IEtherTokenV06 private immutable WETH; constructor(AdapterAddresses memory addresses) public { - REGISTRY = IMooniswapRegistry(addresses.mooniswapRegistry); WETH = IEtherTokenV06(addresses.weth); } 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 c7c159af4a..3ad6c5a6d4 100644 --- a/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts +++ b/contracts/zero-ex/test/transformers/fill_quote_transformer_test.ts @@ -72,7 +72,6 @@ blockchainTests.resets('FillQuoteTransformer', env => { uniswapV2Router: NULL_ADDRESS, uniswapExchangeFactory: NULL_ADDRESS, mStable: NULL_ADDRESS, - mooniswapRegistry: NULL_ADDRESS, weth: NULL_ADDRESS, }, ); From ca63bcc9b0498005b795802877769cc72d0e56de Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 01:21:21 -0400 Subject: [PATCH 41/47] `@0x/migrations`: Remove `mooniswapRegistry` from `BridgeAdapter` addreses --- packages/migrations/src/migration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index 1589defc31..90fb275ff9 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -324,7 +324,6 @@ export async function runMigrationsAsync( uniswapV2Router: NULL_ADDRESS, uniswapExchangeFactory: NULL_ADDRESS, mStable: NULL_ADDRESS, - mooniswapRegistry: NULL_ADDRESS, weth: etherToken.address, }, ); From 05c5acdb159c8ef9395cbcf21950fc304ff9d503 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 03:27:58 -0400 Subject: [PATCH 42/47] `@0x/contracts-zero-ex`: Fix Mooniswap WETH buying bug --- .../src/transformers/bridges/mixins/MixinMooniswap.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol index c58f41b137..4cbe634eae 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol @@ -88,5 +88,10 @@ contract MixinMooniswap is 1, address(0) ); + + // Wrap ETH to WETH. + if (buyToken == WETH) { + WETH.deposit{value:boughtAmount}(); + } } } From 3753b1a7d017ce699057ca44b7ee9c7c1b7c50c3 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 09:45:54 -0400 Subject: [PATCH 43/47] `@0x/asset-swapper`: Increase default error tolerance on quote simulation tests. Run prettier. --- .../contracts/src/MooniswapSampler.sol | 21 +++++++------------ .../utils/market_operation_utils/orders.ts | 5 ++++- .../test/quote_simulation_test.ts | 4 ++-- packages/contract-addresses/addresses.json | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/asset-swapper/contracts/src/MooniswapSampler.sol b/packages/asset-swapper/contracts/src/MooniswapSampler.sol index 0c8d8f6a28..a5767fdff5 100644 --- a/packages/asset-swapper/contracts/src/MooniswapSampler.sol +++ b/packages/asset-swapper/contracts/src/MooniswapSampler.sol @@ -127,7 +127,7 @@ contract MooniswapSampler is returns (IMooniswap pool, uint256[] memory takerTokenAmounts) { _assertValidPair(makerToken, takerToken); - uint256 numSamples = takerTokenAmounts.length; + uint256 numSamples = makerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken; @@ -165,18 +165,11 @@ contract MooniswapSampler is abi.decode(takerTokenData, (address, IMooniswap)); (address mooniswapMakerToken) = abi.decode(makerTokenData, (address)); - (bool success, bytes memory resultData) = - address(this).staticcall(abi.encodeWithSelector( - this.sampleSingleSellFromMooniswapPool.selector, - pool, - mooniswapTakerToken, - mooniswapMakerToken, - _toSingleValueArray(sellAmount) - )); - if (!success) { - return 0; - } - // solhint-disable-next-line indent - return abi.decode(resultData, (uint256[]))[0]; + return sampleSingleSellFromMooniswapPool( + pool, + mooniswapTakerToken, + mooniswapMakerToken, + sellAmount + ); } } 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 b12af98f6a..76f3ee01f5 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -417,7 +417,10 @@ function createKyberBridgeData(fromTokenAddress: string, hint: string): string { } function createMooniswapBridgeData(takerToken: string, poolAddress: string): string { - const encoder = AbiEncoder.create([{ name: 'takerToken', type: 'address' }, { name: 'poolAddress', type: 'address' }]); + const encoder = AbiEncoder.create([ + { name: 'takerToken', type: 'address' }, + { name: 'poolAddress', type: 'address' }, + ]); return encoder.encode({ takerToken, poolAddress }); } 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/addresses.json b/packages/contract-addresses/addresses.json index 8ef15002c0..f8b625fed0 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -196,7 +196,7 @@ "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", - "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" } }, "1337": { From dc66f1b886a0013c1db760e5a63e8610705d6e93 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 11:01:41 -0400 Subject: [PATCH 44/47] `@0x/asset-swapper`: Fix Mooniswap buy sampling bug. --- .../contracts/src/MooniswapSampler.sol | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/asset-swapper/contracts/src/MooniswapSampler.sol b/packages/asset-swapper/contracts/src/MooniswapSampler.sol index a5767fdff5..3b90f049a8 100644 --- a/packages/asset-swapper/contracts/src/MooniswapSampler.sol +++ b/packages/asset-swapper/contracts/src/MooniswapSampler.sol @@ -54,19 +54,9 @@ contract MooniswapSampler is address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken; address mooniswapMakerToken = makerToken == _getWethAddress() ? address(0) : makerToken; - // Find the pool for the pair, ETH is represented - // as address(0) - pool = IMooniswap( - IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) - ); - // If there is no pool then return early - if (address(pool) == address(0)) { - return (pool, makerTokenAmounts); - } for (uint256 i = 0; i < numSamples; i++) { uint256 buyAmount = sampleSingleSellFromMooniswapPool( - pool, mooniswapTakerToken, mooniswapMakerToken, takerTokenAmounts[i] @@ -80,7 +70,6 @@ contract MooniswapSampler is } function sampleSingleSellFromMooniswapPool( - IMooniswap pool, address mooniswapTakerToken, address mooniswapMakerToken, uint256 takerTokenAmount @@ -89,10 +78,17 @@ contract MooniswapSampler is 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) { @@ -101,7 +97,7 @@ contract MooniswapSampler is (bool didSucceed, bytes memory resultData) = address(pool).staticcall.gas(MOONISWAP_CALL_GAS)( abi.encodeWithSelector( - IMooniswap(0).getReturn.selector, + pool.getReturn.selector, mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount @@ -128,28 +124,23 @@ contract MooniswapSampler is { _assertValidPair(makerToken, takerToken); uint256 numSamples = makerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); + takerTokenAmounts = new uint256[](numSamples); address mooniswapTakerToken = takerToken == _getWethAddress() ? address(0) : takerToken; address mooniswapMakerToken = makerToken == _getWethAddress() ? address(0) : makerToken; - // Find the pool for the pair, ETH is represented - // as address(0) - pool = IMooniswap( - IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) - ); - // If there is no pool then return early - if (address(pool) == address(0)) { - return (pool, takerTokenAmounts); - } takerTokenAmounts = _sampleApproximateBuys( ApproximateBuyQuoteOpts({ - makerTokenData: abi.encode(mooniswapMakerToken, pool), - takerTokenData: abi.encode(mooniswapTakerToken, pool), + makerTokenData: abi.encode(mooniswapMakerToken), + takerTokenData: abi.encode(mooniswapTakerToken), getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap }), makerTokenAmounts ); + + pool = IMooniswap( + IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) + ); } function _sampleSellForApproximateBuyFromMooniswap( @@ -161,12 +152,9 @@ contract MooniswapSampler is view returns (uint256 buyAmount) { - (address mooniswapTakerToken, IMooniswap pool) = - abi.decode(takerTokenData, (address, IMooniswap)); - (address mooniswapMakerToken) = - abi.decode(makerTokenData, (address)); + address mooniswapTakerToken = abi.decode(takerTokenData, (address)); + address mooniswapMakerToken = abi.decode(makerTokenData, (address)); return sampleSingleSellFromMooniswapPool( - pool, mooniswapTakerToken, mooniswapMakerToken, sellAmount From bf899d40a0f1c46a4c586f0b476922ca307b3fce Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 13:39:30 -0400 Subject: [PATCH 45/47] `@0x/contracts-zero-ex`: Only deploy `FlashWallet` in `TransformERC20` migration if it does not already exist. --- .../zero-ex/contracts/src/features/TransformERC20Feature.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol index d09f597962..fa77fba85a 100644 --- a/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol +++ b/contracts/zero-ex/contracts/src/features/TransformERC20Feature.sol @@ -76,7 +76,10 @@ contract TransformERC20Feature 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; } From e1a48e80e1cfe81fc32b8d7a8b8a6e1cf7f8b975 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 14:45:03 -0400 Subject: [PATCH 46/47] `@0x/asset-swapper`: Fix mooniswap sell sampling bug --- packages/asset-swapper/contracts/src/MooniswapSampler.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/asset-swapper/contracts/src/MooniswapSampler.sol b/packages/asset-swapper/contracts/src/MooniswapSampler.sol index 3b90f049a8..0c19e7d72b 100644 --- a/packages/asset-swapper/contracts/src/MooniswapSampler.sol +++ b/packages/asset-swapper/contracts/src/MooniswapSampler.sol @@ -67,6 +67,10 @@ contract MooniswapSampler is } makerTokenAmounts[i] = buyAmount; } + + pool = IMooniswap( + IMooniswapRegistry(_getMooniswapAddress()).pools(mooniswapTakerToken, mooniswapMakerToken) + ); } function sampleSingleSellFromMooniswapPool( From bd3387a408492d629fb7b480a9ea604c0083007e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 3 Sep 2020 16:52:28 -0400 Subject: [PATCH 47/47] `@0x/contract-addresses`: Update EP addresses --- packages/contract-addresses/addresses.json | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index f8b625fed0..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": "0x81c0ab53a7352d2e97f682a37cba44e54647eefb", + "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3", "mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c", "transformers": { - "wethTransformer": "0x9fbacfe5cf2ee08a3f7c71c638603b6d5961bfeb", - "payTakerTransformer": "0xc9406e3ec8c4005951e0d9430a2981ca720d76c9", - "affiliateFeeTransformer": "0x2cfdacce1dcf97950a7dd0adad278a1077fa4224", - "fillQuoteTransformer": "0x3ae59e53ea6a2e364ba68e584c8db95134a0021a" + "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", + "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", + "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", + "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" } }, "3": { @@ -88,15 +88,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", + "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", - "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", - "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", - "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" + "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", + "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", + "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" } }, "4": { @@ -138,15 +138,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb", + "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", - "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", - "affiliateFeeTransformer": "0x4581b59a05ba373b9f67676f66bdb5fcd67e7567", - "fillQuoteTransformer": "0xaaeb683b35a36876bd44aea6b704f58614889228" + "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", + "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", + "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", + "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" } }, "42": { @@ -188,15 +188,15 @@ "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", - "exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502", + "exchangeProxyTransformerDeployer": "0x1b62de2dbb5e7aa519e9c442721ecef75702807f", "exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18", "mStableBridge": "0x0000000000000000000000000000000000000000", "mooniswapBridge": "0x0000000000000000000000000000000000000000", "transformers": { - "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", - "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", - "affiliateFeeTransformer": "0xa39b40642e8e00435857a0fe7d0655e08cc2217e", - "fillQuoteTransformer": "0xaf77ff7b00ff528abdcac3f1dcf072de702b758e" + "wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d", + "payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977", + "affiliateFeeTransformer": "0x870893920a96a28d4b63c0a7d06a521e3bd074b3", + "fillQuoteTransformer": "0x71f5b09fe71d496c9e4f24d4de6aba21bf04b000" } }, "1337": {