diff options
author | Amir Bandeali <abandeali1@gmail.com> | 2019-01-22 13:32:59 +0800 |
---|---|---|
committer | Amir Bandeali <abandeali1@gmail.com> | 2019-01-22 13:41:22 +0800 |
commit | efbfd08aa0152bf82d293fab0b073e1d650c9ab1 (patch) | |
tree | b7fea548337a7e1d409ba08917582eec77678c24 /contracts/extensions | |
parent | f9c29c3e7421bc8bcb59316e366249d1b0efb407 (diff) | |
download | dexon-0x-contracts-efbfd08aa0152bf82d293fab0b073e1d650c9ab1.tar.gz dexon-0x-contracts-efbfd08aa0152bf82d293fab0b073e1d650c9ab1.tar.zst dexon-0x-contracts-efbfd08aa0152bf82d293fab0b073e1d650c9ab1.zip |
Separate forwarder into own package
Diffstat (limited to 'contracts/extensions')
38 files changed, 2 insertions, 2613 deletions
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index 2bb468724..1dfaf7a05 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -18,5 +18,5 @@ } } }, - "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder", "OrderMatcher", "OrderValidator"] + "contracts": ["BalanceThresholdFilter", "DutchAuction", "OrderMatcher", "OrderValidator"] } diff --git a/contracts/extensions/contracts/Forwarder/Forwarder.sol b/contracts/extensions/contracts/Forwarder/Forwarder.sol deleted file mode 100644 index 94dec40ed..000000000 --- a/contracts/extensions/contracts/Forwarder/Forwarder.sol +++ /dev/null @@ -1,50 +0,0 @@ -/* - - 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 "./MixinWeth.sol"; -import "./MixinForwarderCore.sol"; -import "./libs/LibConstants.sol"; -import "./MixinAssets.sol"; -import "./MixinExchangeWrapper.sol"; - - -// solhint-disable no-empty-blocks -contract Forwarder is - LibConstants, - MixinWeth, - MixinAssets, - MixinExchangeWrapper, - MixinForwarderCore -{ - constructor ( - address _exchange, - bytes memory _zrxAssetData, - bytes memory _wethAssetData - ) - public - LibConstants( - _exchange, - _zrxAssetData, - _wethAssetData - ) - MixinForwarderCore() - {} -} diff --git a/contracts/extensions/contracts/Forwarder/MixinAssets.sol b/contracts/extensions/contracts/Forwarder/MixinAssets.sol deleted file mode 100644 index 330586ed9..000000000 --- a/contracts/extensions/contracts/Forwarder/MixinAssets.sol +++ /dev/null @@ -1,143 +0,0 @@ -/* - - 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; - -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "@0x/contracts-utils/contracts/src/Ownable.sol"; -import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; -import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; -import "./libs/LibConstants.sol"; -import "./mixins/MAssets.sol"; - - -contract MixinAssets is - Ownable, - LibConstants, - MAssets -{ - using LibBytes for bytes; - - bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)")); - - /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to - /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be - /// used to withdraw assets that were accidentally sent to this contract. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of ERC20 token to withdraw. - function withdrawAsset( - bytes assetData, - uint256 amount - ) - external - onlyOwner - { - transferAssetToSender(assetData, amount); - } - - /// @dev Transfers given amount of asset to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferAssetToSender( - bytes memory assetData, - uint256 amount - ) - internal - { - bytes4 proxyId = assetData.readBytes4(0); - - if (proxyId == ERC20_DATA_ID) { - transferERC20Token(assetData, amount); - } else if (proxyId == ERC721_DATA_ID) { - transferERC721Token(assetData, amount); - } else { - revert("UNSUPPORTED_ASSET_PROXY"); - } - } - - /// @dev Decodes ERC20 assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferERC20Token( - bytes memory assetData, - uint256 amount - ) - internal - { - address token = assetData.readAddress(16); - - // Transfer tokens. - // We do a raw call so we can check the success separate - // from the return data. - bool success = token.call(abi.encodeWithSelector( - ERC20_TRANSFER_SELECTOR, - msg.sender, - amount - )); - require( - success, - "TRANSFER_FAILED" - ); - - // Check return data. - // If there is no return data, we assume the token incorrectly - // does not return a bool. In this case we expect it to revert - // on failure, which was handled above. - // If the token does return data, we require that it is a single - // value that evaluates to true. - assembly { - if returndatasize { - success := 0 - if eq(returndatasize, 32) { - // First 64 bytes of memory are reserved scratch space - returndatacopy(0, 0, 32) - success := mload(0) - } - } - } - require( - success, - "TRANSFER_FAILED" - ); - } - - /// @dev Decodes ERC721 assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferERC721Token( - bytes memory assetData, - uint256 amount - ) - internal - { - require( - amount == 1, - "INVALID_AMOUNT" - ); - // Decode asset data. - address token = assetData.readAddress(16); - uint256 tokenId = assetData.readUint256(36); - - // Perform transfer. - IERC721Token(token).transferFrom( - address(this), - msg.sender, - tokenId - ); - } -} diff --git a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol deleted file mode 100644 index c970c40cf..000000000 --- a/contracts/extensions/contracts/Forwarder/MixinExchangeWrapper.sol +++ /dev/null @@ -1,260 +0,0 @@ -/* - - 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 "./libs/LibConstants.sol"; -import "./mixins/MExchangeWrapper.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibAbiEncoder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; - - -contract MixinExchangeWrapper is - LibAbiEncoder, - LibFillResults, - LibMath, - LibConstants, - 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 - 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 - ) - if success { - 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))) - } - } - // fillResults values will be 0 by default if call was unsuccessful - 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 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 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 marketBuyExactAmountWithWeth( - 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. - // We round up because the exchange rate computed by fillOrder rounds in favor - // of the Maker. In this case we want to overestimate the amount of takerAsset. - uint256 remainingTakerAssetFillAmount = getPartialAmountCeil( - 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 - uint256 makerAssetFilledAmount = totalFillResults.makerAssetFilledAmount; - if (makerAssetFilledAmount >= makerAssetFillAmount) { - break; - } - } - - require( - makerAssetFilledAmount >= makerAssetFillAmount, - "COMPLETE_FILL_FAILED" - ); - 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 marketBuyExactZrxWithWeth( - 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. - // We round up because the exchange rate computed by fillOrder rounds in favor - // of the Maker. In this case we want to overestimate the amount of takerAsset. - uint256 remainingWethSellAmount = getPartialAmountCeil( - 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], - remainingWethSellAmount, - 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; - } - } - - require( - zrxPurchased >= zrxBuyAmount, - "COMPLETE_FILL_FAILED" - ); - return totalFillResults; - } -} diff --git a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol b/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol deleted file mode 100644 index b65e1f7ce..000000000 --- a/contracts/extensions/contracts/Forwarder/MixinForwarderCore.sol +++ /dev/null @@ -1,214 +0,0 @@ -/* - - 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 "./libs/LibConstants.sol"; -import "./mixins/MWeth.sol"; -import "./mixins/MAssets.sol"; -import "./mixins/MExchangeWrapper.sol"; -import "./interfaces/IForwarderCore.sol"; -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; - - -contract MixinForwarderCore is - LibFillResults, - LibMath, - LibConstants, - MWeth, - MAssets, - MExchangeWrapper, - IForwarderCore -{ - using LibBytes for bytes; - - /// @dev Constructor approves ERC20 proxy to transfer ZRX and WETH on this contract's behalf. - constructor () - public - { - address proxyAddress = EXCHANGE.getAssetProxy(ERC20_DATA_ID); - require( - proxyAddress != address(0), - "UNREGISTERED_ASSET_PROXY" - ); - ETHER_TOKEN.approve(proxyAddress, MAX_UINT); - ZRX_TOKEN.approve(proxyAddress, MAX_UINT); - } - - /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. - /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. - /// 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH). - /// Any ETH not spent will be refunded to sender. - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param signatures Proofs that orders have been created by makers. - /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. - /// @param feeSignatures Proofs that feeOrders have been created by makers. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - /// @return Amounts filled and fees paid by maker and taker for both sets of orders. - function marketSellOrdersWithEth( - LibOrder.Order[] memory orders, - bytes[] memory signatures, - LibOrder.Order[] memory feeOrders, - bytes[] memory feeSignatures, - uint256 feePercentage, - address feeRecipient - ) - public - payable - returns ( - FillResults memory orderFillResults, - FillResults memory feeOrderFillResults - ) - { - // Convert ETH to WETH. - convertEthToWeth(); - - uint256 wethSellAmount; - uint256 zrxBuyAmount; - uint256 makerAssetAmountPurchased; - if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { - // Calculate amount of WETH that won't be spent on ETH fees. - wethSellAmount = getPartialAmountFloor( - PERCENTAGE_DENOMINATOR, - safeAdd(PERCENTAGE_DENOMINATOR, feePercentage), - msg.value - ); - // Market sell available WETH. - // ZRX fees are paid with this contract's balance. - orderFillResults = marketSellWeth( - orders, - wethSellAmount, - signatures - ); - // The fee amount must be deducted from the amount transfered back to sender. - makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); - } else { - // 5% of WETH is reserved for filling feeOrders and paying feeRecipient. - wethSellAmount = getPartialAmountFloor( - MAX_WETH_FILL_PERCENTAGE, - PERCENTAGE_DENOMINATOR, - msg.value - ); - // Market sell 95% of WETH. - // ZRX fees are payed with this contract's balance. - orderFillResults = marketSellWeth( - orders, - wethSellAmount, - signatures - ); - // Buy back all ZRX spent on fees. - zrxBuyAmount = orderFillResults.takerFeePaid; - feeOrderFillResults = marketBuyExactZrxWithWeth( - feeOrders, - zrxBuyAmount, - feeSignatures - ); - makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount; - } - - // Transfer feePercentage of total ETH spent on primary orders to feeRecipient. - // Refund remaining ETH to msg.sender. - transferEthFeeAndRefund( - orderFillResults.takerAssetFilledAmount, - feeOrderFillResults.takerAssetFilledAmount, - feePercentage, - feeRecipient - ); - - // Transfer purchased assets to msg.sender. - transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased); - } - - /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction. - /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. - /// Any ETH not spent will be refunded to sender. - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param makerAssetFillAmount Desired amount of makerAsset to purchase. - /// @param signatures Proofs that orders have been created by makers. - /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. - /// @param feeSignatures Proofs that feeOrders have been created by makers. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - /// @return Amounts filled and fees paid by maker and taker for both sets of orders. - function marketBuyOrdersWithEth( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures, - LibOrder.Order[] memory feeOrders, - bytes[] memory feeSignatures, - uint256 feePercentage, - address feeRecipient - ) - public - payable - returns ( - FillResults memory orderFillResults, - FillResults memory feeOrderFillResults - ) - { - // Convert ETH to WETH. - convertEthToWeth(); - - uint256 zrxBuyAmount; - uint256 makerAssetAmountPurchased; - 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 = marketBuyExactZrxWithWeth( - orders, - makerAssetFillAmount, - signatures - ); - // The fee amount must be deducted from the amount transfered back to sender. - makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); - } else { - // Attemp to purchase desired amount of makerAsset. - // ZRX fees are payed with this contract's balance. - orderFillResults = marketBuyExactAmountWithWeth( - orders, - makerAssetFillAmount, - signatures - ); - // Buy back all ZRX spent on fees. - zrxBuyAmount = orderFillResults.takerFeePaid; - feeOrderFillResults = marketBuyExactZrxWithWeth( - feeOrders, - zrxBuyAmount, - feeSignatures - ); - makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount; - } - - // Transfer feePercentage of total ETH spent on primary orders to feeRecipient. - // Refund remaining ETH to msg.sender. - transferEthFeeAndRefund( - orderFillResults.takerAssetFilledAmount, - feeOrderFillResults.takerAssetFilledAmount, - feePercentage, - feeRecipient - ); - - // Transfer purchased assets to msg.sender. - transferAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased); - } -} diff --git a/contracts/extensions/contracts/Forwarder/MixinWeth.sol b/contracts/extensions/contracts/Forwarder/MixinWeth.sol deleted file mode 100644 index 029924019..000000000 --- a/contracts/extensions/contracts/Forwarder/MixinWeth.sol +++ /dev/null @@ -1,113 +0,0 @@ -/* - - 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; - -import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; -import "./libs/LibConstants.sol"; -import "./mixins/MWeth.sol"; - - -contract MixinWeth is - LibMath, - LibConstants, - MWeth -{ - /// @dev Default payabale function, this allows us to withdraw WETH - function () - public - payable - { - require( - msg.sender == address(ETHER_TOKEN), - "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY" - ); - } - - /// @dev Converts message call's ETH value into WETH. - function convertEthToWeth() - internal - { - require( - msg.value > 0, - "INVALID_MSG_VALUE" - ); - ETHER_TOKEN.deposit.value(msg.value)(); - } - - /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient. - /// Refunds any excess ETH to msg.sender. - /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders. - /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - function transferEthFeeAndRefund( - uint256 wethSoldExcludingFeeOrders, - uint256 wethSoldForZrx, - uint256 feePercentage, - address feeRecipient - ) - internal - { - // Ensure feePercentage is less than 5%. - require( - feePercentage <= MAX_FEE_PERCENTAGE, - "FEE_PERCENTAGE_TOO_LARGE" - ); - - // Ensure that no extra WETH owned by this contract has been sold. - uint256 wethSold = safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx); - require( - wethSold <= msg.value, - "OVERSOLD_WETH" - ); - - // Calculate amount of WETH that hasn't been sold. - uint256 wethRemaining = safeSub(msg.value, wethSold); - - // Calculate ETH fee to pay to feeRecipient. - uint256 ethFee = getPartialAmountFloor( - feePercentage, - PERCENTAGE_DENOMINATOR, - wethSoldExcludingFeeOrders - ); - - // Ensure fee is less than amount of WETH remaining. - require( - ethFee <= wethRemaining, - "INSUFFICIENT_ETH_REMAINING" - ); - - // Do nothing if no WETH remaining - if (wethRemaining > 0) { - // Convert remaining WETH to ETH - ETHER_TOKEN.withdraw(wethRemaining); - - // Pay ETH to feeRecipient - if (ethFee > 0) { - feeRecipient.transfer(ethFee); - } - - // Refund remaining ETH to msg.sender. - uint256 ethRefund = safeSub(wethRemaining, ethFee); - if (ethRefund > 0) { - msg.sender.transfer(ethRefund); - } - } - } -} diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol b/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol deleted file mode 100644 index cebfd3706..000000000 --- a/contracts/extensions/contracts/Forwarder/interfaces/IAssets.sol +++ /dev/null @@ -1,34 +0,0 @@ -/* - - 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; - - -contract IAssets { - - /// @dev Withdraws assets from this contract. The contract requires a ZRX balance in order to - /// function optimally, and this function allows the ZRX to be withdrawn by owner. It may also be - /// used to withdraw assets that were accidentally sent to this contract. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of ERC20 token to withdraw. - function withdrawAsset( - bytes assetData, - uint256 amount - ) - external; -} diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol deleted file mode 100644 index 6ce8a1d31..000000000 --- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarder.sol +++ /dev/null @@ -1,30 +0,0 @@ -/* - - 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 "./IForwarderCore.sol"; -import "./IAssets.sol"; - - -// solhint-disable no-empty-blocks -contract IForwarder is - IForwarderCore, - IAssets -{} diff --git a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol b/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol deleted file mode 100644 index 00009d3d5..000000000 --- a/contracts/extensions/contracts/Forwarder/interfaces/IForwarderCore.sol +++ /dev/null @@ -1,80 +0,0 @@ -/* - - 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 "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; - - -contract IForwarderCore { - - /// @dev Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. - /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. - /// 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH). - /// Any ETH not spent will be refunded to sender. - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param signatures Proofs that orders have been created by makers. - /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. - /// @param feeSignatures Proofs that feeOrders have been created by makers. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - /// @return Amounts filled and fees paid by maker and taker for both sets of orders. - function marketSellOrdersWithEth( - LibOrder.Order[] memory orders, - bytes[] memory signatures, - LibOrder.Order[] memory feeOrders, - bytes[] memory feeSignatures, - uint256 feePercentage, - address feeRecipient - ) - public - payable - returns ( - LibFillResults.FillResults memory orderFillResults, - LibFillResults.FillResults memory feeOrderFillResults - ); - - /// @dev Attempt to purchase makerAssetFillAmount of makerAsset by selling ETH provided with transaction. - /// Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. - /// Any ETH not spent will be refunded to sender. - /// @param orders Array of order specifications used containing desired makerAsset and WETH as takerAsset. - /// @param makerAssetFillAmount Desired amount of makerAsset to purchase. - /// @param signatures Proofs that orders have been created by makers. - /// @param feeOrders Array of order specifications containing ZRX as makerAsset and WETH as takerAsset. Used to purchase ZRX for primary order fees. - /// @param feeSignatures Proofs that feeOrders have been created by makers. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - /// @return Amounts filled and fees paid by maker and taker for both sets of orders. - function marketBuyOrdersWithEth( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures, - LibOrder.Order[] memory feeOrders, - bytes[] memory feeSignatures, - uint256 feePercentage, - address feeRecipient - ) - public - payable - returns ( - LibFillResults.FillResults memory orderFillResults, - LibFillResults.FillResults memory feeOrderFillResults - ); -} diff --git a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol b/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol deleted file mode 100644 index 3ff2a03e5..000000000 --- a/contracts/extensions/contracts/Forwarder/libs/LibConstants.sol +++ /dev/null @@ -1,62 +0,0 @@ -/* - - 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; - -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; -import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol"; -import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; - - -contract LibConstants { - - using LibBytes for bytes; - - bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)")); - bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)")); - uint256 constant internal MAX_UINT = 2**256 - 1; - uint256 constant internal PERCENTAGE_DENOMINATOR = 10**18; - uint256 constant internal MAX_FEE_PERCENTAGE = 5 * PERCENTAGE_DENOMINATOR / 100; // 5% - uint256 constant internal MAX_WETH_FILL_PERCENTAGE = 95 * PERCENTAGE_DENOMINATOR / 100; // 95% - - // solhint-disable var-name-mixedcase - IExchange internal EXCHANGE; - IEtherToken internal ETHER_TOKEN; - IERC20Token internal ZRX_TOKEN; - bytes internal ZRX_ASSET_DATA; - bytes internal WETH_ASSET_DATA; - // solhint-enable var-name-mixedcase - - constructor ( - address _exchange, - bytes memory _zrxAssetData, - bytes memory _wethAssetData - ) - public - { - EXCHANGE = IExchange(_exchange); - ZRX_ASSET_DATA = _zrxAssetData; - WETH_ASSET_DATA = _wethAssetData; - - address etherToken = _wethAssetData.readAddress(16); - address zrxToken = _zrxAssetData.readAddress(16); - ETHER_TOKEN = IEtherToken(etherToken); - ZRX_TOKEN = IERC20Token(zrxToken); - } -} diff --git a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol b/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol deleted file mode 100644 index 7a95b78a0..000000000 --- a/contracts/extensions/contracts/Forwarder/libs/LibForwarderErrors.sol +++ /dev/null @@ -1,34 +0,0 @@ -/* - - 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. - -*/ - -// solhint-disable -pragma solidity ^0.4.24; - - -/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. -contract LibForwarderErrors { - string constant FEE_PERCENTAGE_TOO_LARGE = "FEE_PROPORTION_TOO_LARGE"; // Provided fee percentage greater than 5%. - string constant INSUFFICIENT_ETH_REMAINING = "INSUFFICIENT_ETH_REMAINING"; // Not enough ETH remaining to pay feeRecipient. - string constant OVERSOLD_WETH = "OVERSOLD_WETH"; // More WETH sold than provided with current message call. - string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired purchase amount not completely filled (required for ZRX fees only). - string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer failed. - string constant UNSUPPORTED_ASSET_PROXY = "UNSUPPORTED_ASSET_PROXY"; // Proxy in assetData not supported. - string constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY = "DEFAULT_FUNCTION_WETH_CONTRACT_ONLY"; // Fallback function may only be used for WETH withdrawals. - string constant INVALID_MSG_VALUE = "INVALID_MSG_VALUE"; // msg.value must be greater than 0. - string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Amount must equal 1. -} diff --git a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol b/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol deleted file mode 100644 index 1757b37fb..000000000 --- a/contracts/extensions/contracts/Forwarder/mixins/MAssets.sol +++ /dev/null @@ -1,53 +0,0 @@ -/* - - 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; - -import "../interfaces/IAssets.sol"; - - -contract MAssets is - IAssets -{ - /// @dev Transfers given amount of asset to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferAssetToSender( - bytes memory assetData, - uint256 amount - ) - internal; - - /// @dev Decodes ERC20 assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferERC20Token( - bytes memory assetData, - uint256 amount - ) - internal; - - /// @dev Decodes ERC721 assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function transferERC721Token( - bytes memory assetData, - uint256 amount - ) - internal; -} diff --git a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol b/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol deleted file mode 100644 index 7439e8259..000000000 --- a/contracts/extensions/contracts/Forwarder/mixins/MExchangeWrapper.sol +++ /dev/null @@ -1,87 +0,0 @@ -/* - - 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 "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "@0x/contracts-exchange-libs/contracts/src/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 marketBuyExactAmountWithWeth( - 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 marketBuyExactZrxWithWeth( - LibOrder.Order[] memory orders, - uint256 zrxBuyAmount, - bytes[] memory signatures - ) - internal - returns (LibFillResults.FillResults memory totalFillResults); -} diff --git a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol b/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol deleted file mode 100644 index 15d66942f..000000000 --- a/contracts/extensions/contracts/Forwarder/mixins/MWeth.sol +++ /dev/null @@ -1,41 +0,0 @@ -/* - - 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; - - -contract MWeth { - - /// @dev Converts message call's ETH value into WETH. - function convertEthToWeth() - internal; - - /// @dev Transfers feePercentage of WETH spent on primary orders to feeRecipient. - /// Refunds any excess ETH to msg.sender. - /// @param wethSoldExcludingFeeOrders Amount of WETH sold when filling primary orders. - /// @param wethSoldForZrx Amount of WETH sold when purchasing ZRX required for primary order fees. - /// @param feePercentage Percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - /// @param feeRecipient Address that will receive ETH when orders are filled. - function transferEthFeeAndRefund( - uint256 wethSoldExcludingFeeOrders, - uint256 wethSoldForZrx, - uint256 feePercentage, - address feeRecipient - ) - internal; -} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol index b6e14525d..b6e14525d 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/BalanceThresholdFilter.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 68dabafeb..68dabafeb 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol index f2c0caa57..f2c0caa57 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinExchangeCalldata.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol index 4a1bf1fb2..4a1bf1fb2 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol index f78f9c2de..f78f9c2de 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/interfaces/IThresholdAsset.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 1c840136b..1c840136b 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol index 40536d820..40536d820 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/mixins/MExchangeCalldata.sol diff --git a/contracts/extensions/contracts/DutchAuction/DutchAuction.sol b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol index 7631880c0..7631880c0 100644 --- a/contracts/extensions/contracts/DutchAuction/DutchAuction.sol +++ b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol diff --git a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol index 65b36ea65..65b36ea65 100644 --- a/contracts/extensions/contracts/OrderMatcher/MixinAssets.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/MixinAssets.sol diff --git a/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol b/contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol index baf41f650..baf41f650 100644 --- a/contracts/extensions/contracts/OrderMatcher/MixinMatchOrders.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/MixinMatchOrders.sol diff --git a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol b/contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol index 89a7f3c55..89a7f3c55 100644 --- a/contracts/extensions/contracts/OrderMatcher/OrderMatcher.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/OrderMatcher.sol diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol index 9fcc0023a..9fcc0023a 100644 --- a/contracts/extensions/contracts/OrderMatcher/interfaces/IAssets.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IAssets.sol diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol index 7e4a88df8..7e4a88df8 100644 --- a/contracts/extensions/contracts/OrderMatcher/interfaces/IMatchOrders.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IMatchOrders.sol diff --git a/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol index 5e5125dea..5e5125dea 100644 --- a/contracts/extensions/contracts/OrderMatcher/interfaces/IOrderMatcher.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/interfaces/IOrderMatcher.sol diff --git a/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol b/contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol index 77b72398f..77b72398f 100644 --- a/contracts/extensions/contracts/OrderMatcher/libs/LibConstants.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/libs/LibConstants.sol diff --git a/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol b/contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol index 6ea7a3290..6ea7a3290 100644 --- a/contracts/extensions/contracts/OrderMatcher/mixins/MAssets.sol +++ b/contracts/extensions/contracts/src/OrderMatcher/mixins/MAssets.sol diff --git a/contracts/extensions/contracts/OrderValidator/OrderValidator.sol b/contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol index 3297a980f..3297a980f 100644 --- a/contracts/extensions/contracts/OrderValidator/OrderValidator.sol +++ b/contracts/extensions/contracts/src/OrderValidator/OrderValidator.sol diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json index 4395d1fac..e4adf6350 100644 --- a/contracts/extensions/package.json +++ b/contracts/extensions/package.json @@ -32,7 +32,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder|OrderMatcher|OrderValidator).json" + "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|OrderMatcher|OrderValidator).json" }, "repository": { "type": "git", diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts index 329d0f3f3..f2aee4908 100644 --- a/contracts/extensions/src/artifacts/index.ts +++ b/contracts/extensions/src/artifacts/index.ts @@ -2,14 +2,12 @@ import { ContractArtifact } from 'ethereum-types'; import * as BalanceThresholdFilter from '../../generated-artifacts/BalanceThresholdFilter.json'; import * as DutchAuction from '../../generated-artifacts/DutchAuction.json'; -import * as Forwarder from '../../generated-artifacts/Forwarder.json'; import * as OrderMatcher from '../../generated-artifacts/OrderMatcher.json'; import * as OrderValidator from '../../generated-artifacts/OrderValidator.json'; export const artifacts = { BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact, DutchAuction: DutchAuction as ContractArtifact, - Forwarder: Forwarder as ContractArtifact, OrderMatcher: OrderMatcher as ContractArtifact, OrderValidator: OrderValidator as ContractArtifact, }; diff --git a/contracts/extensions/src/wrappers/index.ts b/contracts/extensions/src/wrappers/index.ts index 65aec3ccd..eed198524 100644 --- a/contracts/extensions/src/wrappers/index.ts +++ b/contracts/extensions/src/wrappers/index.ts @@ -1,5 +1,4 @@ export * from '../../generated-wrappers/balance_threshold_filter'; export * from '../../generated-wrappers/dutch_auction'; -export * from '../../generated-wrappers/forwarder'; export * from '../../generated-wrappers/order_matcher'; export * from '../../generated-wrappers/order_validator'; diff --git a/contracts/extensions/test/forwarder.ts b/contracts/extensions/test/forwarder.ts deleted file mode 100644 index 24c219563..000000000 --- a/contracts/extensions/test/forwarder.ts +++ /dev/null @@ -1,1279 +0,0 @@ -import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; -import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20'; -import { DummyERC721TokenContract } from '@0x/contracts-erc721'; -import { artifacts as exchangeArtifacts, ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange'; -import { - chaiSetup, - constants, - ContractName, - ERC20BalancesByOwner, - expectContractCreationFailedAsync, - expectTransactionFailedAsync, - OrderFactory, - provider, - sendTransactionResult, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; - -import { artifacts, ForwarderContract, ForwarderWrapper } from '../src'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; -const MAX_WETH_FILL_PERCENTAGE = 95; - -describe(ContractName.Forwarder, () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - let defaultMakerAssetAddress: string; - let zrxAssetData: string; - let wethAssetData: string; - - let weth: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc20TokenA: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let forwarderContract: ForwarderContract; - let wethContract: WETH9Contract; - let forwarderWrapper: ForwarderWrapper; - let exchangeWrapper: ExchangeWrapper; - - let orderWithoutFee: SignedOrder; - let orderWithFee: SignedOrder; - let feeOrder: SignedOrder; - let orderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let tx: TransactionReceiptWithDecodedLogs; - - let erc721MakerAssetIds: BigNumber[]; - let takerEthBalanceBefore: BigNumber; - let feePercentage: BigNumber; - let gasPrice: BigNumber; - - before(async () => { - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts); - - const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); - const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); - gasPrice = new BigNumber(transaction.gasPrice); - - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 3; - [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - const erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(erc20Artifacts.WETH9, provider, txDefaults); - weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); - erc20Wrapper.addDummyTokenContract(weth); - - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - exchangeArtifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - - defaultMakerAssetAddress = erc20TokenA.address; - const defaultTakerAssetAddress = wethContract.address; - const defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - - const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, - provider, - txDefaults, - exchangeInstance.address, - zrxAssetData, - wethAssetData, - ); - forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); - forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); - const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), - ); - erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - orderWithoutFee = await orderFactory.newSignedOrderAsync(); - feeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - orderWithFee = await orderFactory.newSignedOrderAsync({ - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => { - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - exchangeArtifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - return expectContractCreationFailedAsync( - (ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, - provider, - txDefaults, - exchangeInstance.address, - zrxAssetData, - wethAssetData, - ) as any) as sendTransactionResult, - RevertReason.UnregisteredAssetProxy, - ); - }); - }); - describe('marketSellOrdersWithEth without extra fees', () => { - it('should fill a single order', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue( - ethValue, - MAX_WETH_FILL_PERCENTAGE, - ); - const makerAssetFillAmount = primaryTakerAssetFillAmount - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fill multiple orders', async () => { - const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync(); - const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const ethValue = ordersWithoutFee[0].takerAssetAmount.plus( - ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2), - ); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue( - ethValue, - MAX_WETH_FILL_PERCENTAGE, - ); - const firstTakerAssetFillAmount = ordersWithoutFee[0].takerAssetAmount; - const secondTakerAssetFillAmount = primaryTakerAssetFillAmount.minus(firstTakerAssetFillAmount); - - const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus( - ordersWithoutFee[1].makerAssetAmount - .times(secondTakerAssetFillAmount) - .dividedToIntegerBy(ordersWithoutFee[1].takerAssetAmount), - ); - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fill the order and pay ZRX fees from a single feeOrder', async () => { - const ordersWithFee = [orderWithFee]; - const feeOrders = [feeOrder]; - const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue( - ethValue, - MAX_WETH_FILL_PERCENTAGE, - ); - const makerAssetFillAmount = primaryTakerAssetFillAmount - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - const feeAmount = ForwarderWrapper.getPercentageOfValue( - orderWithFee.takerFee.dividedToIntegerBy(2), - MAX_WETH_FILL_PERCENTAGE, - ); - const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); - const totalEthSpent = primaryTakerAssetFillAmount - .plus(wethSpentOnFeeOrders) - .plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fill the orders and pay ZRX from multiple feeOrders', async () => { - const ordersWithFee = [orderWithFee]; - const ethValue = orderWithFee.takerAssetAmount; - const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2); - const takerAssetAmount = feeOrder.takerAssetAmount - .times(makerAssetAmount) - .dividedToIntegerBy(feeOrder.makerAssetAmount); - - const firstFeeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - makerAssetAmount, - takerAssetAmount, - }); - const secondFeeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - makerAssetAmount, - takerAssetAmount, - }); - const feeOrders = [firstFeeOrder, secondFeeOrder]; - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue( - ethValue, - MAX_WETH_FILL_PERCENTAGE, - ); - const makerAssetFillAmount = primaryTakerAssetFillAmount - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - const feeAmount = ForwarderWrapper.getPercentageOfValue(orderWithFee.takerFee, MAX_WETH_FILL_PERCENTAGE); - const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); - const totalEthSpent = primaryTakerAssetFillAmount - .plus(wethSpentOnFeeOrders) - .plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fill the order when token is ZRX with fees', async () => { - orderWithFee = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - const ordersWithFee = [orderWithFee]; - const feeOrders: SignedOrder[] = []; - const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2); - const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed)); - const takerFeePaid = orderWithFee.takerFee.dividedToIntegerBy(2); - const makerFeePaid = orderWithFee.makerFee.dividedToIntegerBy(2); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount).minus(makerFeePaid), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount).minus(takerFeePaid), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(ethValue), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[forwarderContract.address][zrxToken.address], - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const ethValue = orderWithoutFee.takerAssetAmount.times(2); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const totalEthSpent = orderWithoutFee.takerAssetAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - }); - it('should revert if ZRX cannot be fully repurchased', async () => { - orderWithFee = await orderFactory.newSignedOrderAsync({ - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT), - }); - const ordersWithFee = [orderWithFee]; - feeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - const feeOrders = [feeOrder]; - const ethValue = orderWithFee.takerAssetAmount; - return expectTransactionFailedAsync( - forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, { - value: ethValue, - from: takerAddress, - }), - RevertReason.CompleteFillFailed, - ); - }); - it('should not fill orders with different makerAssetData than the first order', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - const erc721SignedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - }); - const erc20SignedOrder = await orderFactory.newSignedOrderAsync(); - const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder]; - const feeOrders: SignedOrder[] = []; - const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount); - - tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const totalEthSpent = erc20SignedOrder.takerAssetAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - }); - }); - describe('marketSellOrdersWithEth with extra fees', () => { - it('should fill the order and send fee to feeRecipient', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const ethValue = orderWithoutFee.takerAssetAmount.div(2); - - const baseFeePercentage = 2; - feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); - tx = await forwarderWrapper.marketSellOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - { - value: ethValue, - from: takerAddress, - }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue( - ethValue, - MAX_WETH_FILL_PERCENTAGE, - ); - const makerAssetFillAmount = primaryTakerAssetFillAmount - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage); - const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee)); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fail if the fee is set too high', async () => { - const ethValue = orderWithoutFee.takerAssetAmount.div(2); - const baseFeePercentage = 6; - feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - await expectTransactionFailedAsync( - forwarderWrapper.marketSellOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - { from: takerAddress, value: ethValue, gasPrice }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ), - RevertReason.FeePercentageTooLarge, - ); - }); - it('should fail if there is not enough ETH remaining to pay the fee', async () => { - const ethValue = orderWithoutFee.takerAssetAmount.div(2); - const baseFeePercentage = 5; - feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage); - const ordersWithFee = [orderWithFee]; - const feeOrders = [feeOrder]; - await expectTransactionFailedAsync( - forwarderWrapper.marketSellOrdersWithEthAsync( - ordersWithFee, - feeOrders, - { from: takerAddress, value: ethValue, gasPrice }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ), - RevertReason.InsufficientEthRemaining, - ); - }); - }); - describe('marketBuyOrdersWithEth without extra fees', () => { - it('should buy the exact amount of makerAsset in a single order', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2); - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should buy the exact amount of makerAsset in multiple orders', async () => { - const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync(); - const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus( - ordersWithoutFee[1].makerAssetAmount.dividedToIntegerBy(2), - ); - const ethValue = ordersWithoutFee[0].takerAssetAmount.plus( - ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2), - ); - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should buy the exact amount of makerAsset and return excess ETH', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount; - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ethValue.dividedToIntegerBy(2); - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should buy the exact amount of makerAsset and pay ZRX from feeOrders', async () => { - const ordersWithFee = [orderWithFee]; - const feeOrders = [feeOrder]; - const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithFee.takerAssetAmount; - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2); - const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2); - const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); - const totalEthSpent = primaryTakerAssetFillAmount - .plus(wethSpentOnFeeOrders) - .plus(gasPrice.times(tx.gasUsed)); - - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should buy slightly greater than makerAssetAmount when buying ZRX', async () => { - orderWithFee = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - const ordersWithFee = [orderWithFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithFee.takerAssetAmount; - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders( - makerAssetFillAmount, - ordersWithFee, - ); - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - const makerAssetFilledAmount = orderWithFee.makerAssetAmount - .times(primaryTakerAssetFillAmount) - .dividedToIntegerBy(orderWithFee.takerAssetAmount); - const takerFeePaid = orderWithFee.takerFee - .times(primaryTakerAssetFillAmount) - .dividedToIntegerBy(orderWithFee.takerAssetAmount); - const makerFeePaid = orderWithFee.makerFee - .times(primaryTakerAssetFillAmount) - .dividedToIntegerBy(orderWithFee.takerAssetAmount); - const totalZrxPurchased = makerAssetFilledAmount.minus(takerFeePaid); - // Up to 1 wei worth of ZRX will be overbought per order - const maxOverboughtZrx = new BigNumber(1) - .times(orderWithFee.makerAssetAmount) - .dividedToIntegerBy(orderWithFee.takerAssetAmount); - - expect(totalZrxPurchased).to.be.bignumber.gte(makerAssetFillAmount); - expect(totalZrxPurchased).to.be.bignumber.lte(makerAssetFillAmount.plus(maxOverboughtZrx)); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFilledAmount).minus(makerFeePaid), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[forwarderContract.address][zrxToken.address], - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(4); - return expectTransactionFailedAsync( - forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }), - RevertReason.CompleteFillFailed, - ); - }); - it('should buy an ERC721 asset from a single order', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = new BigNumber(1); - const ethValue = orderWithFee.takerAssetAmount; - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - from: takerAddress, - value: ethValue, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - expect(newOwner).to.be.bignumber.equal(takerAddress); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should revert if buying an ERC721 asset when later orders contain different makerAssetData', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - }); - const differentMakerAssetDataOrder = await orderFactory.newSignedOrderAsync(); - const ordersWithoutFee = [orderWithoutFee, differentMakerAssetDataOrder]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = new BigNumber(1).plus(differentMakerAssetDataOrder.makerAssetAmount); - const ethValue = orderWithFee.takerAssetAmount; - return expectTransactionFailedAsync( - forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }), - RevertReason.CompleteFillFailed, - ); - }); - it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - orderWithFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - const ordersWithFee = [orderWithFee]; - const feeOrders = [feeOrder]; - const makerAssetFillAmount = orderWithFee.makerAssetAmount; - const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount; - const feeAmount = orderWithFee.takerFee; - const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); - const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders); - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed)); - - expect(newOwner).to.be.bignumber.equal(takerAddress); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - orderWithFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - const ordersWithFee = [orderWithFee]; - const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2); - const takerAssetAmount = feeOrder.takerAssetAmount - .times(makerAssetAmount) - .dividedToIntegerBy(feeOrder.makerAssetAmount); - - const firstFeeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - makerAssetAmount, - takerAssetAmount, - }); - const secondFeeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - makerAssetAmount, - takerAssetAmount, - }); - const feeOrders = [firstFeeOrder, secondFeeOrder]; - - const makerAssetFillAmount = orderWithFee.makerAssetAmount; - const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount; - const feeAmount = orderWithFee.takerFee; - const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); - const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders); - - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed)); - - expect(newOwner).to.be.bignumber.equal(takerAddress); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('Should buy slightly greater MakerAsset when exchange rate is rounded', async () => { - // The 0x Protocol contracts round the exchange rate in favor of the Maker. - // In this case, the taker must round up how much they're going to spend, which - // in turn increases the amount of MakerAsset being purchased. - // Example: - // The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T. - // For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset. - // To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset. - // However, the Taker can only spend whole units. - // Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset. - // Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset. - // - // The forwarding contract will opt for the second option, which overbuys, to ensure the taker - // receives at least the amount of MakerAsset they requested. - // - // Construct test case using values from example above - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber('30'), - takerAssetAmount: new BigNumber('20'), - makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const desiredMakerAssetFillAmount = new BigNumber('5'); - const makerAssetFillAmount = new BigNumber('6'); - const ethValue = new BigNumber('4'); - // Execute test case - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - desiredMakerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - ); - // Fetch end balances and construct expected outputs - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - // Validate test case - expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX', async () => { - // See the test case above for a detailed description of this case. - // The difference here is that the MakerAsset is ZRX. We expect the same result as above, - // but this tests a different code path. - // - // Construct test case using values from example above - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber('30'), - takerAssetAmount: new BigNumber('20'), - makerAssetData: zrxAssetData, - takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const desiredMakerAssetFillAmount = new BigNumber('5'); - const makerAssetFillAmount = new BigNumber('6'); - const ethValue = new BigNumber('4'); - // Execute test case - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - desiredMakerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - ); - // Fetch end balances and construct expected outputs - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - // Validate test case - expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => { - // Order taken from a transaction on mainnet that failed due to a rounding error. - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber('268166666666666666666'), - takerAssetAmount: new BigNumber('219090625878836371'), - makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - // The taker will receive more than the desired amount of makerAsset due to rounding - const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000'); - const ethValue = new BigNumber('4084971271824171'); - const makerAssetFillAmount = ethValue - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - // Execute test case - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - desiredMakerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - ); - // Fetch end balances and construct expected outputs - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - // Validate test case - expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('Should buy slightly greater MakerAsset when exchange rate is rounded, and MakerAsset is ZRX (Regression Test)', async () => { - // Order taken from a transaction on mainnet that failed due to a rounding error. - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber('268166666666666666666'), - takerAssetAmount: new BigNumber('219090625878836371'), - makerAssetData: zrxAssetData, - takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - // The taker will receive more than the desired amount of makerAsset due to rounding - const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000'); - const ethValue = new BigNumber('4084971271824171'); - const makerAssetFillAmount = ethValue - .times(orderWithoutFee.makerAssetAmount) - .dividedToIntegerBy(orderWithoutFee.takerAssetAmount); - // Execute test case - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - desiredMakerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - ); - // Fetch end balances and construct expected outputs - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - // Validate test case - expect(makerAssetFillAmount).to.be.bignumber.greaterThan(desiredMakerAssetFillAmount); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('Should buy correct MakerAsset when exchange rate is NOT rounded, and MakerAsset is ZRX (Regression Test)', async () => { - // An extra unit of TakerAsset was sent to the exchange contract to account for rounding errors, in Forwarder v1. - // Specifically, the takerFillAmount was calculated using Floor(desiredMakerAmount * exchangeRate) + 1 - // We have since changed this to be Ceil(desiredMakerAmount * exchangeRate) - // These calculations produce different results when `desiredMakerAmount * exchangeRate` is an integer. - // - // This test verifies that `ceil` is sufficient: - // Let TakerAssetAmount = MakerAssetAmount * 2 - // -> exchangeRate = TakerAssetAmount / MakerAssetAmount = (2*MakerAssetAmount)/MakerAssetAmount = 2 - // .: desiredMakerAmount * exchangeRate is an integer. - // - // Construct test case using values from example above - orderWithoutFee = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber('30'), - takerAssetAmount: new BigNumber('60'), - makerAssetData: zrxAssetData, - takerAssetData: assetDataUtils.encodeERC20AssetData(weth.address), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - }); - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = new BigNumber('5'); - const ethValue = new BigNumber('10'); - // Execute test case - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, { - value: ethValue, - from: takerAddress, - }); - // Fetch end balances and construct expected outputs - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const primaryTakerAssetFillAmount = ethValue; - const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed)); - // Validate test case - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - }); - describe('marketBuyOrdersWithEth with extra fees', () => { - it('should buy an asset and send fee to feeRecipient', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount; - - const baseFeePercentage = 2; - feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage); - const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); - tx = await forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - makerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ); - const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); - const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2); - const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage); - const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed)); - - expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee)); - expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), - ); - expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should fail if the fee is set too high', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount; - - const baseFeePercentage = 6; - feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage); - await expectTransactionFailedAsync( - forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - makerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ), - RevertReason.FeePercentageTooLarge, - ); - }); - it('should fail if there is not enough ETH remaining to pay the fee', async () => { - const ordersWithoutFee = [orderWithoutFee]; - const feeOrders: SignedOrder[] = []; - const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); - const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2); - - const baseFeePercentage = 2; - feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage); - await expectTransactionFailedAsync( - forwarderWrapper.marketBuyOrdersWithEthAsync( - ordersWithoutFee, - feeOrders, - makerAssetFillAmount, - { - value: ethValue, - from: takerAddress, - }, - { feePercentage, feeRecipient: feeRecipientAddress }, - ), - RevertReason.InsufficientEthRemaining, - ); - }); - }); - describe('withdrawAsset', () => { - it('should allow owner to withdraw ERC20 tokens', async () => { - const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address]; - await forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: owner }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[owner][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[owner][zrxToken.address].plus(zrxWithdrawAmount), - ); - expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[forwarderContract.address][zrxToken.address].minus(zrxWithdrawAmount), - ); - }); - it('should revert if not called by owner', async () => { - const zrxWithdrawAmount = erc20Balances[forwarderContract.address][zrxToken.address]; - await expectTransactionFailedAsync( - forwarderWrapper.withdrawAssetAsync(zrxAssetData, zrxWithdrawAmount, { from: makerAddress }), - RevertReason.OnlyContractOwner, - ); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/extensions/test/utils/forwarder_wrapper.ts b/contracts/extensions/test/utils/forwarder_wrapper.ts deleted file mode 100644 index 207106852..000000000 --- a/contracts/extensions/test/utils/forwarder_wrapper.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { artifacts as erc20Artifacts } from '@0x/contracts-erc20'; -import { artifacts as erc721Artifacts } from '@0x/contracts-erc721'; -import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange'; -import { constants, formatters, LogDecoder, MarketSellOrders } from '@0x/contracts-test-utils'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ForwarderContract } from '../../generated-wrappers/forwarder'; -import { artifacts } from '../../src/artifacts'; - -export class ForwarderWrapper { - private readonly _web3Wrapper: Web3Wrapper; - private readonly _forwarderContract: ForwarderContract; - private readonly _logDecoder: LogDecoder; - public static getPercentageOfValue(value: BigNumber, percentage: number): BigNumber { - const numerator = constants.PERCENTAGE_DENOMINATOR.times(percentage).dividedToIntegerBy(100); - const newValue = value.times(numerator).dividedToIntegerBy(constants.PERCENTAGE_DENOMINATOR); - return newValue; - } - public static getWethForFeeOrders(feeAmount: BigNumber, feeOrders: SignedOrder[]): BigNumber { - let wethAmount = new BigNumber(0); - let remainingFeeAmount = feeAmount; - _.forEach(feeOrders, feeOrder => { - const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee); - if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) { - wethAmount = wethAmount.plus( - feeOrder.takerAssetAmount - .times(remainingFeeAmount) - .dividedBy(feeAvailable) - .integerValue(BigNumber.ROUND_CEIL), - ); - remainingFeeAmount = new BigNumber(0); - } else if (!remainingFeeAmount.isZero()) { - wethAmount = wethAmount.plus(feeOrder.takerAssetAmount); - remainingFeeAmount = remainingFeeAmount.minus(feeAvailable); - } - }); - return wethAmount; - } - private static _createOptimizedOrders(signedOrders: SignedOrder[]): MarketSellOrders { - _.forEach(signedOrders, (signedOrder, index) => { - signedOrder.takerAssetData = constants.NULL_BYTES; - if (index > 0) { - signedOrder.makerAssetData = constants.NULL_BYTES; - } - }); - const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT); - return params; - } - private static _createOptimizedZrxOrders(signedOrders: SignedOrder[]): MarketSellOrders { - _.forEach(signedOrders, signedOrder => { - signedOrder.makerAssetData = constants.NULL_BYTES; - signedOrder.takerAssetData = constants.NULL_BYTES; - }); - const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT); - return params; - } - constructor(contractInstance: ForwarderContract, provider: Provider) { - this._forwarderContract = contractInstance; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper, { - ...artifacts, - ...erc20Artifacts, - ...erc721Artifacts, - ...exchangeArtifacts, - }); - } - public async marketSellOrdersWithEthAsync( - orders: SignedOrder[], - feeOrders: SignedOrder[], - txData: TxDataPayable, - opts: { feePercentage?: BigNumber; feeRecipient?: string } = {}, - ): Promise<TransactionReceiptWithDecodedLogs> { - const params = ForwarderWrapper._createOptimizedOrders(orders); - const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders); - const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage; - const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient; - const txHash = await this._forwarderContract.marketSellOrdersWithEth.sendTransactionAsync( - params.orders, - params.signatures, - feeParams.orders, - feeParams.signatures, - feePercentage, - feeRecipient, - txData, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async marketBuyOrdersWithEthAsync( - orders: SignedOrder[], - feeOrders: SignedOrder[], - makerAssetFillAmount: BigNumber, - txData: TxDataPayable, - opts: { feePercentage?: BigNumber; feeRecipient?: string } = {}, - ): Promise<TransactionReceiptWithDecodedLogs> { - const params = ForwarderWrapper._createOptimizedOrders(orders); - const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders); - const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage; - const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient; - const txHash = await this._forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync( - params.orders, - makerAssetFillAmount, - params.signatures, - feeParams.orders, - feeParams.signatures, - feePercentage, - feeRecipient, - txData, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async withdrawAssetAsync( - assetData: string, - amount: BigNumber, - txData: TxDataPayable, - ): Promise<TransactionReceiptWithDecodedLogs> { - const txHash = await this._forwarderContract.withdrawAsset.sendTransactionAsync(assetData, amount, txData); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } -} diff --git a/contracts/extensions/test/utils/index.ts b/contracts/extensions/test/utils/index.ts index d5d529c11..6ba9a2300 100644 --- a/contracts/extensions/test/utils/index.ts +++ b/contracts/extensions/test/utils/index.ts @@ -1,3 +1,2 @@ export * from './balance_threshold_wrapper'; export * from './dutch_auction_test_wrapper'; -export * from './forwarder_wrapper'; diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json index ed9b4fbe1..0c06c6902 100644 --- a/contracts/extensions/tsconfig.json +++ b/contracts/extensions/tsconfig.json @@ -9,7 +9,6 @@ "files": [ "./generated-artifacts/BalanceThresholdFilter.json", "./generated-artifacts/DutchAuction.json", - "./generated-artifacts/Forwarder.json", "./generated-artifacts/OrderMatcher.json", "./generated-artifacts/OrderValidator.json" ], |