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",