diff options
6 files changed, 355 insertions, 166 deletions
diff --git a/packages/contracts/src/2.0.0/forwarder/Forwarder.sol b/packages/contracts/src/2.0.0/forwarder/Forwarder.sol index 4405d5e46..d3721884c 100644 --- a/packages/contracts/src/2.0.0/forwarder/Forwarder.sol +++ b/packages/contracts/src/2.0.0/forwarder/Forwarder.sol @@ -23,6 +23,7 @@ import "./MixinWeth.sol"; import "./MixinForwarderCore.sol"; import "./MixinConstants.sol"; import "./MixinAssets.sol"; +import "./MixinExchangeWrapper.sol"; // solhint-disable no-empty-blocks @@ -30,6 +31,7 @@ contract Forwarder is MixinConstants, MixinWeth, MixinAssets, + MixinExchangeWrapper, MixinForwarderCore { diff --git a/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol new file mode 100644 index 000000000..5c0a606e6 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol @@ -0,0 +1,250 @@ +/* + + Copyright 2018 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.4.24; +pragma experimental ABIEncoderV2; + +import "./mixins/MConstants.sol"; +import "./mixins/MExchangeWrapper.sol"; +import "../protocol/Exchange/libs/LibAbiEncoder.sol"; +import "../protocol/Exchange/libs/LibOrder.sol"; +import "../protocol/Exchange/libs/LibFillResults.sol"; +import "../protocol/Exchange/libs/LibMath.sol"; + + +contract MixinExchangeWrapper is + LibAbiEncoder, + LibFillResults, + LibMath, + MConstants, + MExchangeWrapper +{ + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (FillResults memory fillResults) + { + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abiEncodeFillOrder( + order, + takerAssetFillAmount, + signature + ); + + address exchange = address(EXCHANGE); + + // Call `fillOrder` and handle any exceptions gracefully + assembly { + let success := call( + gas, // forward all gas, TODO: look into gas consumption of assert/throw + exchange, // call address of Exchange contract + 0, // transfer 0 wei + add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) + mload(fillOrderCalldata), // length of input + fillOrderCalldata, // write output over input + 128 // output size is 128 bytes + ) + switch success + case 0 { + mstore(fillResults, 0) + mstore(add(fillResults, 32), 0) + mstore(add(fillResults, 64), 0) + mstore(add(fillResults, 96), 0) + } + case 1 { + mstore(fillResults, mload(fillOrderCalldata)) + mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) + mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) + mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96))) + } + } + return fillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param wethSellAmount Desired amount of WETH to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellWeth( + LibOrder.Order[] memory orders, + uint256 wethSellAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + bytes memory wethAssetData = WETH_ASSET_DATA; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i < ordersLength; i++) { + + // We assume that asset being sold by taker is WETH for each order. + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of WETH to sell + uint256 remainingTakerAssetFillAmount = safeSub(wethSellAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of WETH + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount == wethSellAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyWithWeth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + bytes memory wethAssetData = WETH_ASSET_DATA; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i < ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // We assume that asset being sold by taker is WETH for each order. + orders[i].makerAssetData = makerAssetData; + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmount( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + if (totalFillResults.makerAssetFilledAmount == makerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee + /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). + /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX + /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. + /// @param zrxBuyAmount Desired amount of ZRX to buy. + /// @param signatures Proofs that orders have been created by makers. + /// @return totalFillResults Amounts filled and fees paid by maker and taker. + function marketBuyZrxWithWeth( + LibOrder.Order[] memory orders, + uint256 zrxBuyAmount, + bytes[] memory signatures + ) + internal + returns (FillResults memory totalFillResults) + { + // Do nothing if zrxBuyAmount == 0 + if (zrxBuyAmount == 0) { + return totalFillResults; + } + + bytes memory zrxAssetData = ZRX_ASSET_DATA; + bytes memory wethAssetData = WETH_ASSET_DATA; + uint256 zrxPurchased = 0; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i < ordersLength; i++) { + + // All of these are ZRX/WETH, so we can drop the respective assetData from calldata. + orders[i].makerAssetData = zrxAssetData; + orders[i].takerAssetData = wethAssetData; + + // Calculate the remaining amount of ZRX to buy. + uint256 remainingZrxBuyAmount = safeSub(zrxBuyAmount, zrxPurchased); + + // Convert the remaining amount of ZRX to buy into remaining amount + // of WETH to sell, assuming entire amount can be sold in the current order. + uint256 remainingWethSellAmount = getPartialAmount( + orders[i].takerAssetAmount, + safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees + remainingZrxBuyAmount + ); + + // Attempt to sell the remaining amount of WETH. + FillResults memory singleFillResult = fillOrderNoThrow( + orders[i], + safeAdd(remainingWethSellAmount, 1), + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker. + addFillResults(totalFillResults, singleFillResult); + zrxPurchased = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid); + + // Stop execution if the entire amount of ZRX has been bought. + if (zrxPurchased >= zrxBuyAmount) { + break; + } + } + + return totalFillResults; + } +}
\ No newline at end of file diff --git a/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol b/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol index 2ec2e2638..e9acef7a2 100644 --- a/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol +++ b/packages/contracts/src/2.0.0/forwarder/MixinForwarderCore.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "./mixins/MWeth.sol"; import "./mixins/MAssets.sol"; import "./mixins/MConstants.sol"; +import "./mixins/MExchangeWrapper.sol"; import "./mixins/MForwarderCore.sol"; import "../utils/LibBytes/LibBytes.sol"; import "../protocol/Exchange/libs/LibOrder.sol"; @@ -35,6 +36,7 @@ contract MixinForwarderCore is MConstants, MWeth, MAssets, + MExchangeWrapper, MForwarderCore { @@ -115,7 +117,7 @@ contract MixinForwarderCore is ); // Buy back all ZRX spent on fees. zrxBuyAmount = orderFillResults.takerFeePaid; - feeOrderFillResults = marketBuyZrx( + feeOrderFillResults = marketBuyZrxWithWeth( feeOrders, zrxBuyAmount, feeSignatures @@ -178,7 +180,7 @@ contract MixinForwarderCore is if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { // If the makerAsset is ZRX, it is not necessary to pay fees out of this // contracts's ZRX balance because fees are factored into the price of the order. - orderFillResults = marketBuyZrx( + orderFillResults = marketBuyZrxWithWeth( orders, makerAssetFillAmount, signatures @@ -188,14 +190,14 @@ contract MixinForwarderCore is } else { // Attemp to purchase desired amount of makerAsset. // ZRX fees are payed with this contract's balance. - orderFillResults = marketBuyAsset( + orderFillResults = marketBuyWithWeth( orders, makerAssetFillAmount, signatures ); // Buy back all ZRX spent on fees. zrxBuyAmount = orderFillResults.takerFeePaid; - feeOrderFillResults = marketBuyZrx( + feeOrderFillResults = marketBuyZrxWithWeth( feeOrders, zrxBuyAmount, feeSignatures @@ -223,130 +225,6 @@ contract MixinForwarderCore is transferPurchasedAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased); } - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param wethSellAmount Desired amount of WETH to sell. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by maker and taker. - function marketSellWeth( - LibOrder.Order[] memory orders, - uint256 wethSellAmount, - bytes[] memory signatures - ) - internal - returns (FillResults memory fillResults) - { - // `marketSellOrders` uses the first order's takerAssetData for all passed in orders. - orders[0].takerAssetData = WETH_ASSET_DATA; - - // All orders are required to have the same makerAssetData. We save on gas by reusing the makerAssetData of the first order. - uint256 ordersLength = orders.length; - for (uint256 i = 0; i < ordersLength; i++) { - orders[i].makerAssetData = orders[0].makerAssetData; - } - - // Sell WETH until entire amount has been sold or all orders have been filled. - fillResults = EXCHANGE.marketSellOrdersNoThrow( - orders, - wethSellAmount, - signatures - ); - - return fillResults; - } - - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param makerAssetFillAmount Desired amount of makerAsset to buy. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by maker and taker. - function marketBuyAsset( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures - ) - internal - returns (FillResults memory fillResults) - { - bytes memory wethAssetData = WETH_ASSET_DATA; - - // All orders are required to have WETH as takerAssetData. We save on gas by populating the orders here, rather than passing in any extra calldata. - uint256 ordersLength = orders.length; - for (uint256 i = 0; i < ordersLength; i++) { - orders[i].takerAssetData = wethAssetData; - } - - // Purchase makerAsset until entire amount has been bought or all orders have been filled. - fillResults = EXCHANGE.marketBuyOrdersNoThrow( - orders, - makerAssetFillAmount, - signatures - ); - - return fillResults; - } - - /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee - /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). - /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX - /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. - /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. - /// @param zrxBuyAmount Desired amount of ZRX to buy. - /// @param signatures Proofs that orders have been created by makers. - /// @return totalFillResults Amounts filled and fees paid by maker and taker. - function marketBuyZrx( - LibOrder.Order[] memory orders, - uint256 zrxBuyAmount, - bytes[] memory signatures - ) - internal - returns (FillResults memory totalFillResults) - { - // Do nothing if zrxBuyAmount == 0 - if (zrxBuyAmount == 0) { - return totalFillResults; - } - - bytes memory zrxAssetData = ZRX_ASSET_DATA; - bytes memory wethAssetData = WETH_ASSET_DATA; - uint256 zrxPurchased = 0; - - uint256 ordersLength = orders.length; - for (uint256 i = 0; i < ordersLength; i++) { - - // All of these are ZRX/WETH, so we can drop the respective assetData from calldata. - orders[i].makerAssetData = zrxAssetData; - orders[i].takerAssetData = wethAssetData; - - // Calculate the remaining amount of ZRX to buy. - uint256 remainingZrxBuyAmount = safeSub(zrxBuyAmount, zrxPurchased); - - // Convert the remaining amount of ZRX to buy into remaining amount - // of WETH to sell, assuming entire amount can be sold in the current order. - uint256 remainingWethSellAmount = getPartialAmount( - orders[i].takerAssetAmount, - safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees - remainingZrxBuyAmount - ); - - // Attempt to sell the remaining amount of WETH. - FillResults memory singleFillResult = EXCHANGE.fillOrderNoThrow( - orders[i], - safeAdd(remainingWethSellAmount, 1), - signatures[i] - ); - - // Update amounts filled and fees paid by maker and taker. - addFillResults(totalFillResults, singleFillResult); - zrxPurchased = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid); - - // Stop execution if the entire amount of ZRX has been bought. - if (zrxPurchased >= zrxBuyAmount) { - break; - } - } - - return totalFillResults; - } - /// @dev Ensures that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold. /// @param orderFillResults Amounts filled and fees paid for primary orders. /// @param feeOrderFillResults Amounts filled and fees paid for fee orders. diff --git a/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol b/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol new file mode 100644 index 000000000..0fa90fd82 --- /dev/null +++ b/packages/contracts/src/2.0.0/forwarder/mixins/MExchangeWrapper.sol @@ -0,0 +1,87 @@ +/* + + Copyright 2018 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.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/libs/LibOrder.sol"; +import "../../protocol/Exchange/libs/LibFillResults.sol"; + + +contract MExchangeWrapper { + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults); + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param wethSellAmount Desired amount of WETH to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellWeth( + LibOrder.Order[] memory orders, + uint256 wethSellAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyWithWeth( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); + + /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee + /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). + /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX + /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. + /// The asset being sold by taker must always be WETH. + /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. + /// @param zrxBuyAmount Desired amount of ZRX to buy. + /// @param signatures Proofs that orders have been created by makers. + /// @return totalFillResults Amounts filled and fees paid by maker and taker. + function marketBuyZrxWithWeth( + LibOrder.Order[] memory orders, + uint256 zrxBuyAmount, + bytes[] memory signatures + ) + internal + returns (LibFillResults.FillResults memory totalFillResults); +}
\ No newline at end of file diff --git a/packages/contracts/src/2.0.0/forwarder/mixins/MForwarderCore.sol b/packages/contracts/src/2.0.0/forwarder/mixins/MForwarderCore.sol index 9dee15481..0f5cd9c66 100644 --- a/packages/contracts/src/2.0.0/forwarder/mixins/MForwarderCore.sol +++ b/packages/contracts/src/2.0.0/forwarder/mixins/MForwarderCore.sol @@ -28,43 +28,15 @@ contract MForwarderCore is IForwarderCore { - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param wethSellAmount Desired amount of WETH to sell. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by maker and taker. - function marketSellWeth( - LibOrder.Order[] memory orders, - uint256 wethSellAmount, - bytes[] memory signatures + /// @dev Ensures that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold. + /// @param orderFillResults Amounts filled and fees paid for primary orders. + /// @param feeOrderFillResults Amounts filled and fees paid for fee orders. + /// @param zrxBuyAmount The amount of ZRX that needed to be repurchased after filling primary orders. + function assertValidFillResults( + LibFillResults.FillResults memory orderFillResults, + LibFillResults.FillResults memory feeOrderFillResults, + uint256 zrxBuyAmount ) internal - returns (LibFillResults.FillResults memory fillResults); - - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param makerAssetFillAmount Desired amount of makerAsset to buy. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by maker and taker. - function marketBuyAsset( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures - ) - internal - returns (LibFillResults.FillResults memory fillResults); - - /// @dev Buys zrxBuyAmount of ZRX fee tokens, taking into account ZRX fees for each order. This will guarantee - /// that at least zrxBuyAmount of ZRX is purchased (sometimes slightly over due to rounding issues). - /// It is possible that a request to buy 200 ZRX will require purchasing 202 ZRX - /// as 2 ZRX is required to purchase the 200 ZRX fee tokens. This guarantees at least 200 ZRX for future purchases. - /// @param orders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. - /// @param zrxBuyAmount Desired amount of ZRX to buy. - /// @param signatures Proofs that orders have been created by makers. - /// @return totalFillResults Amounts filled and fees paid by maker and taker. - function marketBuyZrx( - LibOrder.Order[] memory orders, - uint256 zrxBuyAmount, - bytes[] memory signatures - ) - internal - returns (LibFillResults.FillResults memory totalFillResults); + view; } diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol index adb56799a..a0882b108 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol @@ -57,7 +57,7 @@ contract MixinWrapperFunctions is return fillResults; } - /// @dev Fills an order with specified parameters and ECDSA signature. + /// @dev Fills the input order. /// Returns false if the transaction would otherwise revert. /// @param order Order struct containing order specifications. /// @param takerAssetFillAmount Desired amount of takerAsset to sell. |