diff options
Diffstat (limited to 'contracts/core')
30 files changed, 12 insertions, 4378 deletions
diff --git a/contracts/core/README.md b/contracts/core/README.md index 922ea21ad..d055705c2 100644 --- a/contracts/core/README.md +++ b/contracts/core/README.md @@ -8,8 +8,6 @@ Contracts that make up and interact with version 2.0.0 of the protocol can be fo * [protocol](./contracts/protocol) * This directory contains the contracts that make up version 2.0.0. A full specification can be found [here](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). -* [extensions](./contracts/extensions) - * This directory contains contracts that interact with the 2.0.0 contracts and will be used in production, such as the [Forwarder](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarder-specification.md) contract. * [test](./contracts/test) * This directory contains mocks and other contracts that are used solely for testing contracts within the other directories. diff --git a/contracts/core/compiler.json b/contracts/core/compiler.json index 3f869024c..10e5bb0a1 100644 --- a/contracts/core/compiler.json +++ b/contracts/core/compiler.json @@ -20,14 +20,11 @@ }, "contracts": [ "AssetProxyOwner", - "DutchAuction", "ERC20Proxy", "ERC721Proxy", "Exchange", - "Forwarder", "MixinAuthorizable", "MultiAssetProxy", - "OrderValidator", "TestAssetProxyOwner", "TestAssetProxyDispatcher", "TestExchangeInternals", diff --git a/contracts/core/contracts/extensions/DutchAuction/DutchAuction.sol b/contracts/core/contracts/extensions/DutchAuction/DutchAuction.sol deleted file mode 100644 index 9c9f3990a..000000000 --- a/contracts/core/contracts/extensions/DutchAuction/DutchAuction.sol +++ /dev/null @@ -1,205 +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-interfaces/contracts/protocol/Exchange/IExchange.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol"; -import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol"; -import "@0x/contracts-utils/contracts/utils/SafeMath/SafeMath.sol"; - - -contract DutchAuction is - SafeMath -{ - using LibBytes for bytes; - - // solhint-disable var-name-mixedcase - IExchange internal EXCHANGE; - - struct AuctionDetails { - uint256 beginTimeSeconds; // Auction begin unix timestamp: sellOrder.makerAssetData - uint256 endTimeSeconds; // Auction end unix timestamp: sellOrder.expiryTimeSeconds - uint256 beginAmount; // Auction begin amount: sellOrder.makerAssetData - uint256 endAmount; // Auction end amount: sellOrder.takerAssetAmount - uint256 currentAmount; // Calculated amount given block.timestamp - uint256 currentTimeSeconds; // block.timestamp - } - - constructor (address _exchange) - public - { - EXCHANGE = IExchange(_exchange); - } - - /// @dev Matches the buy and sell orders at an amount given the following: the current block time, the auction - /// start time and the auction begin amount. The sell order is a an order at the lowest amount - /// at the end of the auction. Excess from the match is transferred to the seller. - /// Over time the price moves from beginAmount to endAmount given the current block.timestamp. - /// sellOrder.expiryTimeSeconds is the end time of the auction. - /// sellOrder.takerAssetAmount is the end amount of the auction (lowest possible amount). - /// sellOrder.makerAssetData is the ABI encoded Asset Proxy data with the following data appended - /// buyOrder.makerAssetData is the buyers bid on the auction, must meet the amount for the current block timestamp - /// (uint256 beginTimeSeconds, uint256 beginAmount). - /// This function reverts in the following scenarios: - /// * Auction has not started (auctionDetails.currentTimeSeconds < auctionDetails.beginTimeSeconds) - /// * Auction has expired (auctionDetails.endTimeSeconds < auctionDetails.currentTimeSeconds) - /// * Amount is invalid: Buy order amount is too low (buyOrder.makerAssetAmount < auctionDetails.currentAmount) - /// * Amount is invalid: Invalid begin amount (auctionDetails.beginAmount > auctionDetails.endAmount) - /// * Any failure in the 0x Match Orders - /// @param buyOrder The Buyer's order. This order is for the current expected price of the auction. - /// @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction). - /// @param buySignature Proof that order was created by the buyer. - /// @param sellSignature Proof that order was created by the seller. - /// @return matchedFillResults amounts filled and fees paid by maker and taker of matched orders. - function matchOrders( - LibOrder.Order memory buyOrder, - LibOrder.Order memory sellOrder, - bytes memory buySignature, - bytes memory sellSignature - ) - public - returns (LibFillResults.MatchedFillResults memory matchedFillResults) - { - AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder); - // Ensure the auction has not yet started - require( - auctionDetails.currentTimeSeconds >= auctionDetails.beginTimeSeconds, - "AUCTION_NOT_STARTED" - ); - // Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early - require( - sellOrder.expirationTimeSeconds > auctionDetails.currentTimeSeconds, - "AUCTION_EXPIRED" - ); - // Validate the buyer amount is greater than the current auction amount - require( - buyOrder.makerAssetAmount >= auctionDetails.currentAmount, - "INVALID_AMOUNT" - ); - // Match orders, maximally filling `buyOrder` - matchedFillResults = EXCHANGE.matchOrders( - buyOrder, - sellOrder, - buySignature, - sellSignature - ); - // The difference in sellOrder.takerAssetAmount and current amount is given as spread to the matcher - // This may include additional spread from the buyOrder.makerAssetAmount and the currentAmount. - // e.g currentAmount is 30, sellOrder.takerAssetAmount is 10 and buyOrder.makerAssetamount is 40. - // 10 (40-30) is returned to the buyer, 20 (30-10) sent to the seller and 10 has previously - // been transferred to the seller during matchOrders - uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount; - if (leftMakerAssetSpreadAmount > 0) { - // ERC20 Asset data itself is encoded as follows: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 1 * 32 | function parameters: | - // | | 4 | 12 | 1. token address padding | - // | | 16 | 20 | 2. token address | - bytes memory assetData = sellOrder.takerAssetData; - address token = assetData.readAddress(16); - // Calculate the excess from the buy order. This can occur if the buyer sends in a higher - // amount than the calculated current amount - uint256 buyerExcessAmount = safeSub(buyOrder.makerAssetAmount, auctionDetails.currentAmount); - uint256 sellerExcessAmount = safeSub(leftMakerAssetSpreadAmount, buyerExcessAmount); - // Return the difference between auctionDetails.currentAmount and sellOrder.takerAssetAmount - // to the seller - if (sellerExcessAmount > 0) { - IERC20Token(token).transfer(sellOrder.makerAddress, sellerExcessAmount); - } - // Return the difference between buyOrder.makerAssetAmount and auctionDetails.currentAmount - // to the buyer - if (buyerExcessAmount > 0) { - IERC20Token(token).transfer(buyOrder.makerAddress, buyerExcessAmount); - } - } - return matchedFillResults; - } - - /// @dev Calculates the Auction Details for the given order - /// @param order The sell order - /// @return AuctionDetails - function getAuctionDetails( - LibOrder.Order memory order - ) - public - returns (AuctionDetails memory auctionDetails) - { - uint256 makerAssetDataLength = order.makerAssetData.length; - // It is unknown the encoded data of makerAssetData, we assume the last 64 bytes - // are the Auction Details encoding. - // Auction Details is encoded as follows: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Params | | 2 * 32 | parameters: | - // | | -64 | 32 | 1. auction begin unix timestamp | - // | | -32 | 32 | 2. auction begin begin amount | - // ERC20 asset data length is 4+32, 64 for auction details results in min length 100 - require( - makerAssetDataLength >= 100, - "INVALID_ASSET_DATA" - ); - uint256 auctionBeginTimeSeconds = order.makerAssetData.readUint256(makerAssetDataLength - 64); - uint256 auctionBeginAmount = order.makerAssetData.readUint256(makerAssetDataLength - 32); - // Ensure the auction has a valid begin time - require( - order.expirationTimeSeconds > auctionBeginTimeSeconds, - "INVALID_BEGIN_TIME" - ); - uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds; - // Ensure the auction goes from high to low - uint256 minAmount = order.takerAssetAmount; - require( - auctionBeginAmount > minAmount, - "INVALID_AMOUNT" - ); - uint256 amountDelta = auctionBeginAmount-minAmount; - // solhint-disable-next-line not-rely-on-time - uint256 timestamp = block.timestamp; - auctionDetails.beginTimeSeconds = auctionBeginTimeSeconds; - auctionDetails.endTimeSeconds = order.expirationTimeSeconds; - auctionDetails.beginAmount = auctionBeginAmount; - auctionDetails.endAmount = minAmount; - auctionDetails.currentTimeSeconds = timestamp; - - uint256 remainingDurationSeconds = order.expirationTimeSeconds-timestamp; - if (timestamp < auctionBeginTimeSeconds) { - // If the auction has not yet begun the current amount is the auctionBeginAmount - auctionDetails.currentAmount = auctionBeginAmount; - } else if (timestamp >= order.expirationTimeSeconds) { - // If the auction has ended the current amount is the minAmount. - // Auction end time is guaranteed by 0x Exchange due to the order expiration - auctionDetails.currentAmount = minAmount; - } else { - auctionDetails.currentAmount = safeAdd( - minAmount, - safeDiv( - safeMul(remainingDurationSeconds, amountDelta), - auctionDurationSeconds - ) - ); - } - return auctionDetails; - } -} diff --git a/contracts/core/contracts/extensions/Forwarder/Forwarder.sol b/contracts/core/contracts/extensions/Forwarder/Forwarder.sol deleted file mode 100644 index 94dec40ed..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/Forwarder/MixinAssets.sol b/contracts/core/contracts/extensions/Forwarder/MixinAssets.sol deleted file mode 100644 index 3ebf75161..000000000 --- a/contracts/core/contracts/extensions/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/utils/LibBytes/LibBytes.sol"; -import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC721Token/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/core/contracts/extensions/Forwarder/MixinExchangeWrapper.sol b/contracts/core/contracts/extensions/Forwarder/MixinExchangeWrapper.sol deleted file mode 100644 index 210eb14c2..000000000 --- a/contracts/core/contracts/extensions/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-libs/contracts/libs/LibAbiEncoder.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-libs/contracts/libs/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/core/contracts/extensions/Forwarder/MixinForwarderCore.sol b/contracts/core/contracts/extensions/Forwarder/MixinForwarderCore.sol deleted file mode 100644 index bab78d79b..000000000 --- a/contracts/core/contracts/extensions/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/utils/LibBytes/LibBytes.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-libs/contracts/libs/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/core/contracts/extensions/Forwarder/MixinWeth.sol b/contracts/core/contracts/extensions/Forwarder/MixinWeth.sol deleted file mode 100644 index 2a281f3ae..000000000 --- a/contracts/core/contracts/extensions/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-libs/contracts/libs/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/core/contracts/extensions/Forwarder/interfaces/IAssets.sol b/contracts/core/contracts/extensions/Forwarder/interfaces/IAssets.sol deleted file mode 100644 index 1e034c003..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/Forwarder/interfaces/IForwarder.sol b/contracts/core/contracts/extensions/Forwarder/interfaces/IForwarder.sol deleted file mode 100644 index f5a26e2ba..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/Forwarder/interfaces/IForwarderCore.sol b/contracts/core/contracts/extensions/Forwarder/interfaces/IForwarderCore.sol deleted file mode 100644 index eede20bb8..000000000 --- a/contracts/core/contracts/extensions/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-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/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/core/contracts/extensions/Forwarder/libs/LibConstants.sol b/contracts/core/contracts/extensions/Forwarder/libs/LibConstants.sol deleted file mode 100644 index 4a81abf76..000000000 --- a/contracts/core/contracts/extensions/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/utils/LibBytes/LibBytes.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol"; -import "@0x/contracts-tokens/contracts/tokens/EtherToken/IEtherToken.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC20Token/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/core/contracts/extensions/Forwarder/libs/LibForwarderErrors.sol b/contracts/core/contracts/extensions/Forwarder/libs/LibForwarderErrors.sol deleted file mode 100644 index fb3ade1db..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/Forwarder/mixins/MAssets.sol b/contracts/core/contracts/extensions/Forwarder/mixins/MAssets.sol deleted file mode 100644 index 9e7f80d97..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/Forwarder/mixins/MExchangeWrapper.sol b/contracts/core/contracts/extensions/Forwarder/mixins/MExchangeWrapper.sol deleted file mode 100644 index d9e71786a..000000000 --- a/contracts/core/contracts/extensions/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-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; - - -contract MExchangeWrapper { - - /// @dev Fills the input order. - /// Returns false if the transaction would otherwise revert. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - /// @return Amounts filled and fees paid by maker and taker. - function fillOrderNoThrow( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal - returns (LibFillResults.FillResults memory fillResults); - - /// @dev Synchronously executes multiple calls of fillOrder until total amount of WETH has been sold by taker. - /// Returns false if the transaction would otherwise revert. - /// @param orders Array of order specifications. - /// @param wethSellAmount Desired amount of WETH to sell. - /// @param signatures Proofs that orders have been signed by makers. - /// @return Amounts filled and fees paid by makers and taker. - function marketSellWeth( - LibOrder.Order[] memory orders, - uint256 wethSellAmount, - bytes[] memory signatures - ) - internal - returns (LibFillResults.FillResults memory totalFillResults); - - /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. - /// Returns false if the transaction would otherwise revert. - /// The asset being sold by taker must always be WETH. - /// @param orders Array of order specifications. - /// @param makerAssetFillAmount Desired amount of makerAsset to buy. - /// @param signatures Proofs that orders have been signed by makers. - /// @return Amounts filled and fees paid by makers and taker. - function 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/core/contracts/extensions/Forwarder/mixins/MWeth.sol b/contracts/core/contracts/extensions/Forwarder/mixins/MWeth.sol deleted file mode 100644 index 88e77be4e..000000000 --- a/contracts/core/contracts/extensions/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/core/contracts/extensions/OrderValidator/OrderValidator.sol b/contracts/core/contracts/extensions/OrderValidator/OrderValidator.sol deleted file mode 100644 index 33dd1326c..000000000 --- a/contracts/core/contracts/extensions/OrderValidator/OrderValidator.sol +++ /dev/null @@ -1,218 +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-interfaces/contracts/protocol/Exchange/IExchange.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol"; -import "@0x/contracts-tokens/contracts/tokens/ERC721Token/IERC721Token.sol"; -import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol"; - - -contract OrderValidator { - - 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)")); - - struct TraderInfo { - uint256 makerBalance; // Maker's balance of makerAsset - uint256 makerAllowance; // Maker's allowance to corresponding AssetProxy - uint256 takerBalance; // Taker's balance of takerAsset - uint256 takerAllowance; // Taker's allowance to corresponding AssetProxy - uint256 makerZrxBalance; // Maker's balance of ZRX - uint256 makerZrxAllowance; // Maker's allowance of ZRX to ERC20Proxy - uint256 takerZrxBalance; // Taker's balance of ZRX - uint256 takerZrxAllowance; // Taker's allowance of ZRX to ERC20Proxy - } - - // solhint-disable var-name-mixedcase - IExchange internal EXCHANGE; - bytes internal ZRX_ASSET_DATA; - // solhint-enable var-name-mixedcase - - constructor (address _exchange, bytes memory _zrxAssetData) - public - { - EXCHANGE = IExchange(_exchange); - ZRX_ASSET_DATA = _zrxAssetData; - } - - /// @dev Fetches information for order and maker/taker of order. - /// @param order The order structure. - /// @param takerAddress Address that will be filling the order. - /// @return OrderInfo and TraderInfo instances for given order. - function getOrderAndTraderInfo(LibOrder.Order memory order, address takerAddress) - public - view - returns (LibOrder.OrderInfo memory orderInfo, TraderInfo memory traderInfo) - { - orderInfo = EXCHANGE.getOrderInfo(order); - traderInfo = getTraderInfo(order, takerAddress); - return (orderInfo, traderInfo); - } - - /// @dev Fetches information for all passed in orders and the makers/takers of each order. - /// @param orders Array of order specifications. - /// @param takerAddresses Array of taker addresses corresponding to each order. - /// @return Arrays of OrderInfo and TraderInfo instances that correspond to each order. - function getOrdersAndTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses) - public - view - returns (LibOrder.OrderInfo[] memory ordersInfo, TraderInfo[] memory tradersInfo) - { - ordersInfo = EXCHANGE.getOrdersInfo(orders); - tradersInfo = getTradersInfo(orders, takerAddresses); - return (ordersInfo, tradersInfo); - } - - /// @dev Fetches balance and allowances for maker and taker of order. - /// @param order The order structure. - /// @param takerAddress Address that will be filling the order. - /// @return Balances and allowances of maker and taker of order. - function getTraderInfo(LibOrder.Order memory order, address takerAddress) - public - view - returns (TraderInfo memory traderInfo) - { - (traderInfo.makerBalance, traderInfo.makerAllowance) = getBalanceAndAllowance(order.makerAddress, order.makerAssetData); - (traderInfo.takerBalance, traderInfo.takerAllowance) = getBalanceAndAllowance(takerAddress, order.takerAssetData); - bytes memory zrxAssetData = ZRX_ASSET_DATA; - (traderInfo.makerZrxBalance, traderInfo.makerZrxAllowance) = getBalanceAndAllowance(order.makerAddress, zrxAssetData); - (traderInfo.takerZrxBalance, traderInfo.takerZrxAllowance) = getBalanceAndAllowance(takerAddress, zrxAssetData); - return traderInfo; - } - - /// @dev Fetches balances and allowances of maker and taker for each provided order. - /// @param orders Array of order specifications. - /// @param takerAddresses Array of taker addresses corresponding to each order. - /// @return Array of balances and allowances for maker and taker of each order. - function getTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses) - public - view - returns (TraderInfo[] memory) - { - uint256 ordersLength = orders.length; - TraderInfo[] memory tradersInfo = new TraderInfo[](ordersLength); - for (uint256 i = 0; i != ordersLength; i++) { - tradersInfo[i] = getTraderInfo(orders[i], takerAddresses[i]); - } - return tradersInfo; - } - - /// @dev Fetches token balances and allowances of an address to given assetProxy. Supports ERC20 and ERC721. - /// @param target Address to fetch balances and allowances of. - /// @param assetData Encoded data that can be decoded by a specified proxy contract when transferring asset. - /// @return Balance of asset and allowance set to given proxy of asset. - /// For ERC721 tokens, these values will always be 1 or 0. - function getBalanceAndAllowance(address target, bytes memory assetData) - public - view - returns (uint256 balance, uint256 allowance) - { - bytes4 assetProxyId = assetData.readBytes4(0); - address token = assetData.readAddress(16); - address assetProxy = EXCHANGE.getAssetProxy(assetProxyId); - - if (assetProxyId == ERC20_DATA_ID) { - // Query balance - balance = IERC20Token(token).balanceOf(target); - - // Query allowance - allowance = IERC20Token(token).allowance(target, assetProxy); - } else if (assetProxyId == ERC721_DATA_ID) { - uint256 tokenId = assetData.readUint256(36); - - // Query owner of tokenId - address owner = getERC721TokenOwner(token, tokenId); - - // Set balance to 1 if tokenId is owned by target - balance = target == owner ? 1 : 0; - - // Check if ERC721Proxy is approved to spend tokenId - bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy); - - // Set alowance to 1 if ERC721Proxy is approved to spend tokenId - allowance = isApproved ? 1 : 0; - } else { - revert("UNSUPPORTED_ASSET_PROXY"); - } - return (balance, allowance); - } - - /// @dev Fetches token balances and allowances of an address for each given assetProxy. Supports ERC20 and ERC721. - /// @param target Address to fetch balances and allowances of. - /// @param assetData Array of encoded byte arrays that can be decoded by a specified proxy contract when transferring asset. - /// @return Balances and allowances of assets. - /// For ERC721 tokens, these values will always be 1 or 0. - function getBalancesAndAllowances(address target, bytes[] memory assetData) - public - view - returns (uint256[] memory, uint256[] memory) - { - uint256 length = assetData.length; - uint256[] memory balances = new uint256[](length); - uint256[] memory allowances = new uint256[](length); - for (uint256 i = 0; i != length; i++) { - (balances[i], allowances[i]) = getBalanceAndAllowance(target, assetData[i]); - } - return (balances, allowances); - } - - /// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token. - /// @param token Address of ERC721 token. - /// @param tokenId The identifier for the specific NFT. - /// @return Owner of tokenId or null address if unowned. - function getERC721TokenOwner(address token, uint256 tokenId) - public - view - returns (address owner) - { - assembly { - // load free memory pointer - let cdStart := mload(64) - - // bytes4(keccak256(ownerOf(uint256))) = 0x6352211e - mstore(cdStart, 0x6352211e00000000000000000000000000000000000000000000000000000000) - mstore(add(cdStart, 4), tokenId) - - // staticcall `ownerOf(tokenId)` - // `ownerOf` will revert if tokenId is not owned - let success := staticcall( - gas, // forward all gas - token, // call token contract - cdStart, // start of calldata - 36, // length of input is 36 bytes - cdStart, // write output over input - 32 // size of output is 32 bytes - ) - - // Success implies that tokenId is owned - // Copy owner from return data if successful - if success { - owner := mload(cdStart) - } - } - - // Owner initialized to address(0), no need to modify if call is unsuccessful - return owner; - } -} diff --git a/contracts/core/package.json b/contracts/core/package.json index 62547c311..b7077f4b4 100644 --- a/contracts/core/package.json +++ b/contracts/core/package.json @@ -33,7 +33,7 @@ "lint-contracts": "solhint contracts/**/**/**/**/*.sol" }, "config": { - "abis": "generated-artifacts/@(AssetProxyOwner|DutchAuction|ERC20Proxy|ERC721Proxy|Forwarder|Exchange|MixinAuthorizable|MultiAssetProxy|OrderValidator|TestAssetProxyOwner|TestAssetProxyDispatcher|TestExchangeInternals|TestStaticCallReceiver).json" + "abis": "generated-artifacts/@(AssetProxyOwner|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiAssetProxy|TestSignatureValidator|TestAssetProxyOwner|TestAssetProxyDispatcher|TestExchangeInternals|TestStaticCallReceiver).json" }, "repository": { "type": "git", diff --git a/contracts/core/src/artifacts/index.ts b/contracts/core/src/artifacts/index.ts index 7b598a6d9..c5d12f10b 100644 --- a/contracts/core/src/artifacts/index.ts +++ b/contracts/core/src/artifacts/index.ts @@ -1,14 +1,11 @@ import { ContractArtifact } from 'ethereum-types'; import * as AssetProxyOwner from '../../generated-artifacts/AssetProxyOwner.json'; -import * as DutchAuction from '../../generated-artifacts/DutchAuction.json'; import * as ERC20Proxy from '../../generated-artifacts/ERC20Proxy.json'; import * as ERC721Proxy from '../../generated-artifacts/ERC721Proxy.json'; import * as Exchange from '../../generated-artifacts/Exchange.json'; -import * as Forwarder from '../../generated-artifacts/Forwarder.json'; import * as MixinAuthorizable from '../../generated-artifacts/MixinAuthorizable.json'; import * as MultiAssetProxy from '../../generated-artifacts/MultiAssetProxy.json'; -import * as OrderValidator from '../../generated-artifacts/OrderValidator.json'; import * as TestAssetProxyDispatcher from '../../generated-artifacts/TestAssetProxyDispatcher.json'; import * as TestAssetProxyOwner from '../../generated-artifacts/TestAssetProxyOwner.json'; import * as TestExchangeInternals from '../../generated-artifacts/TestExchangeInternals.json'; @@ -17,14 +14,11 @@ import * as TestStaticCallReceiver from '../../generated-artifacts/TestStaticCal export const artifacts = { AssetProxyOwner: AssetProxyOwner as ContractArtifact, - DutchAuction: DutchAuction as ContractArtifact, ERC20Proxy: ERC20Proxy as ContractArtifact, ERC721Proxy: ERC721Proxy as ContractArtifact, Exchange: Exchange as ContractArtifact, - Forwarder: Forwarder as ContractArtifact, MixinAuthorizable: MixinAuthorizable as ContractArtifact, MultiAssetProxy: MultiAssetProxy as ContractArtifact, - OrderValidator: OrderValidator as ContractArtifact, TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact, TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact, TestExchangeInternals: TestExchangeInternals as ContractArtifact, diff --git a/contracts/core/src/index.ts b/contracts/core/src/index.ts index d55f08ea2..ba813e7ca 100644 --- a/contracts/core/src/index.ts +++ b/contracts/core/src/index.ts @@ -1,2 +1,3 @@ export * from './artifacts'; export * from './wrappers'; +export * from '../test/utils'; diff --git a/contracts/core/src/wrappers/index.ts b/contracts/core/src/wrappers/index.ts index ce3714ed7..01b121054 100644 --- a/contracts/core/src/wrappers/index.ts +++ b/contracts/core/src/wrappers/index.ts @@ -1,11 +1,8 @@ export * from '../../generated-wrappers/asset_proxy_owner'; -export * from '../../generated-wrappers/dutch_auction'; export * from '../../generated-wrappers/erc20_proxy'; export * from '../../generated-wrappers/erc721_proxy'; export * from '../../generated-wrappers/exchange'; -export * from '../../generated-wrappers/forwarder'; export * from '../../generated-wrappers/mixin_authorizable'; -export * from '../../generated-wrappers/order_validator'; export * from '../../generated-wrappers/test_asset_proxy_dispatcher'; export * from '../../generated-wrappers/test_asset_proxy_owner'; export * from '../../generated-wrappers/test_exchange_internals'; diff --git a/contracts/core/test/exchange/signature_validator.ts b/contracts/core/test/exchange/signature_validator.ts index ae372d677..9313fca6c 100644 --- a/contracts/core/test/exchange/signature_validator.ts +++ b/contracts/core/test/exchange/signature_validator.ts @@ -1,3 +1,5 @@ +import { ValidatorContract, WalletContract } from '@0x/contracts-examples'; +import { artifacts as examplesArtifacts } from '@0x/contracts-examples'; import { addressUtils, chaiSetup, @@ -22,8 +24,6 @@ import { TestSignatureValidatorContract, TestSignatureValidatorSignatureValidatorApprovalEventArgs, TestStaticCallReceiverContract, - ValidatorContract, - WalletContract, } from '../../src'; chaiSetup.configure(); @@ -62,13 +62,13 @@ describe('MixinSignatureValidator', () => { txDefaults, ); testWallet = await WalletContract.deployFrom0xArtifactAsync( - artifacts.Wallet, + examplesArtifacts.Wallet, provider, txDefaults, signerAddress, ); testValidator = await ValidatorContract.deployFrom0xArtifactAsync( - artifacts.Validator, + examplesArtifacts.Validator, provider, txDefaults, signerAddress, diff --git a/contracts/core/test/exchange/transactions.ts b/contracts/core/test/exchange/transactions.ts index ad0ca41c2..746f3cb04 100644 --- a/contracts/core/test/exchange/transactions.ts +++ b/contracts/core/test/exchange/transactions.ts @@ -1,3 +1,4 @@ +import { artifacts as examplesArtifacts, ExchangeWrapperContract, WhitelistContract } from '@0x/contracts-examples'; import { chaiSetup, constants, @@ -21,8 +22,6 @@ import * as _ from 'lodash'; import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { ExchangeWrapperContract } from '../../generated-wrappers/exchange_wrapper'; -import { WhitelistContract } from '../../generated-wrappers/whitelist'; import { artifacts } from '../../src/artifacts'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; @@ -222,7 +221,7 @@ describe('Exchange transactions', () => { before(async () => { exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( - artifacts.ExchangeWrapper, + examplesArtifacts.ExchangeWrapper, provider, txDefaults, exchange.address, @@ -336,7 +335,7 @@ describe('Exchange transactions', () => { before(async () => { whitelist = await WhitelistContract.deployFrom0xArtifactAsync( - artifacts.Whitelist, + examplesArtifacts.Whitelist, provider, txDefaults, exchange.address, diff --git a/contracts/core/test/extensions/dutch_auction.ts b/contracts/core/test/extensions/dutch_auction.ts deleted file mode 100644 index b396d4206..000000000 --- a/contracts/core/test/extensions/dutch_auction.ts +++ /dev/null @@ -1,455 +0,0 @@ -import { - chaiSetup, - constants, - ContractName, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - getLatestBlockTimestampAsync, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - WETH9Contract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, generatePseudoRandomSalt } 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 ethAbi = require('ethereumjs-abi'); -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { DutchAuctionContract } from '../../generated-wrappers/dutch_auction'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -describe(ContractName.DutchAuction, () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - let defaultMakerAssetAddress: string; - - let zrxToken: DummyERC20TokenContract; - let erc20TokenA: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let dutchAuctionContract: DutchAuctionContract; - let wethContract: WETH9Contract; - - let sellerOrderFactory: OrderFactory; - let buyerOrderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let currentBlockTimestamp: number; - let auctionBeginTimeSeconds: BigNumber; - let auctionEndTimeSeconds: BigNumber; - let auctionBeginAmount: BigNumber; - let auctionEndAmount: BigNumber; - let sellOrder: SignedOrder; - let buyOrder: SignedOrder; - let erc721MakerAssetIds: BigNumber[]; - const tenMinutesInSeconds = 10 * 60; - - function extendMakerAssetData(makerAssetData: string, beginTimeSeconds: BigNumber, beginAmount: BigNumber): string { - return ethUtil.bufferToHex( - Buffer.concat([ - ethUtil.toBuffer(makerAssetData), - ethUtil.toBuffer( - (ethAbi as any).rawEncode( - ['uint256', 'uint256'], - [beginTimeSeconds.toString(), beginAmount.toString()], - ), - ), - ]), - ); - } - - before(async () => { - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 2; - [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - [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(tokensArtifacts.WETH9, provider, txDefaults); - erc20Wrapper.addDummyTokenContract(wethContract as any); - - const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - const 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, - }); - - const dutchAuctionInstance = await DutchAuctionContract.deployFrom0xArtifactAsync( - artifacts.DutchAuction, - provider, - txDefaults, - exchangeInstance.address, - ); - dutchAuctionContract = new DutchAuctionContract( - dutchAuctionInstance.abi, - dutchAuctionInstance.address, - provider, - ); - - defaultMakerAssetAddress = erc20TokenA.address; - const defaultTakerAssetAddress = wethContract.address; - - // Set up taker WETH balance and allowance - await web3Wrapper.awaitTransactionSuccessAsync( - await wethContract.deposit.sendTransactionAsync({ - from: takerAddress, - value: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT), - }), - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await wethContract.approve.sendTransactionAsync( - erc20Proxy.address, - constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - { from: takerAddress }, - ), - ); - web3Wrapper.abiDecoder.addABI(exchangeInstance.abi); - web3Wrapper.abiDecoder.addABI(zrxToken.abi); - erc20Wrapper.addTokenOwnerAddress(dutchAuctionContract.address); - - currentBlockTimestamp = await getLatestBlockTimestampAsync(); - // Default auction begins 10 minutes ago - auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp).minus(tenMinutesInSeconds); - // Default auction ends 10 from now - auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp).plus(tenMinutesInSeconds); - auctionBeginAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT); - auctionEndAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT); - - // Default sell order and buy order are exact mirrors - const sellerDefaultOrderParams = { - salt: generatePseudoRandomSalt(), - exchangeAddress: exchangeInstance.address, - makerAddress, - feeRecipientAddress, - // taker address or sender address should be set to the ducth auction contract - takerAddress: dutchAuctionContract.address, - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), - takerAssetAmount: auctionEndAmount, - expirationTimeSeconds: auctionEndTimeSeconds, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }; - // Default buy order is for the auction begin price - const buyerDefaultOrderParams = { - ...sellerDefaultOrderParams, - makerAddress: takerAddress, - makerAssetData: sellerDefaultOrderParams.takerAssetData, - takerAssetData: sellerDefaultOrderParams.makerAssetData, - makerAssetAmount: auctionBeginAmount, - takerAssetAmount: sellerDefaultOrderParams.makerAssetAmount, - }; - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; - sellerOrderFactory = new OrderFactory(makerPrivateKey, sellerDefaultOrderParams); - buyerOrderFactory = new OrderFactory(takerPrivateKey, buyerDefaultOrderParams); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - sellOrder = await sellerOrderFactory.newSignedOrderAsync(); - buyOrder = await buyerOrderFactory.newSignedOrderAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('matchOrders', () => { - it('should be worth the begin price at the begining of the auction', async () => { - auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp + 2); - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - }); - const auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionBeginAmount); - expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount); - }); - it('should be be worth the end price at the end of the auction', async () => { - auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2); - auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds); - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - expirationTimeSeconds: auctionEndTimeSeconds, - }); - const auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionEndAmount); - expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount); - }); - it('should match orders at current amount and send excess to buyer', async () => { - const beforeAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - buyOrder = await buyerOrderFactory.newSignedOrderAsync({ - makerAssetAmount: beforeAuctionDetails.currentAmount.times(2), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - ); - const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[dutchAuctionContract.address][wethContract.address]).to.be.bignumber.equal( - constants.ZERO_AMOUNT, - ); - // HACK gte used here due to a bug in ganache where the timestamp can change - // between multiple calls to the same block. Which can move the amount in our case - // ref: https://github.com/trufflesuite/ganache-core/issues/111 - expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte( - erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount), - ); - expect(newBalances[takerAddress][wethContract.address]).to.be.bignumber.gte( - erc20Balances[takerAddress][wethContract.address].minus(beforeAuctionDetails.currentAmount), - ); - }); - it('maker fees on sellOrder are paid to the fee receipient', async () => { - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - makerFee: new BigNumber(1), - }); - const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ); - await web3Wrapper.awaitTransactionSuccessAsync(txHash); - const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte( - erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(sellOrder.makerFee), - ); - }); - it('maker fees on buyOrder are paid to the fee receipient', async () => { - buyOrder = await buyerOrderFactory.newSignedOrderAsync({ - makerFee: new BigNumber(1), - }); - const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ); - await web3Wrapper.awaitTransactionSuccessAsync(txHash); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte( - erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(buyOrder.makerFee), - ); - }); - it('should revert when auction expires', async () => { - auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds * 2); - auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds); - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - expirationTimeSeconds: auctionEndTimeSeconds, - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - }); - return expectTransactionFailedAsync( - dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - RevertReason.AuctionExpired, - ); - }); - it('cannot be filled for less than the current price', async () => { - buyOrder = await buyerOrderFactory.newSignedOrderAsync({ - makerAssetAmount: sellOrder.takerAssetAmount, - }); - return expectTransactionFailedAsync( - dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - RevertReason.AuctionInvalidAmount, - ); - }); - it('auction begin amount must be higher than final amount ', async () => { - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - takerAssetAmount: auctionBeginAmount.plus(1), - }); - return expectTransactionFailedAsync( - dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - RevertReason.AuctionInvalidAmount, - ); - }); - it('begin time is less than end time', async () => { - auctionBeginTimeSeconds = new BigNumber(auctionEndTimeSeconds).plus(tenMinutesInSeconds); - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - expirationTimeSeconds: auctionEndTimeSeconds, - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - }); - return expectTransactionFailedAsync( - dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - RevertReason.AuctionInvalidBeginTime, - ); - }); - it('asset data contains auction parameters', async () => { - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - }); - return expectTransactionFailedAsync( - dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - RevertReason.InvalidAssetData, - ); - }); - describe('ERC721', () => { - it('should match orders when ERC721', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - sellOrder = await sellerOrderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - makerAssetData: extendMakerAssetData( - assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - auctionBeginTimeSeconds, - auctionBeginAmount, - ), - }); - buyOrder = await buyerOrderFactory.newSignedOrderAsync({ - takerAssetAmount: new BigNumber(1), - takerAssetData: sellOrder.makerAssetData, - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await dutchAuctionContract.matchOrders.sendTransactionAsync( - buyOrder, - sellOrder, - buyOrder.signature, - sellOrder.signature, - { - from: takerAddress, - }, - ), - ); - const afterAuctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder); - const newBalances = await erc20Wrapper.getBalancesAsync(); - // HACK gte used here due to a bug in ganache where the timestamp can change - // between multiple calls to the same block. Which can move the amount in our case - // ref: https://github.com/trufflesuite/ganache-core/issues/111 - expect(newBalances[makerAddress][wethContract.address]).to.be.bignumber.gte( - erc20Balances[makerAddress][wethContract.address].plus(afterAuctionDetails.currentAmount), - ); - const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwner).to.be.bignumber.equal(takerAddress); - }); - }); - }); -}); diff --git a/contracts/core/test/extensions/forwarder.ts b/contracts/core/test/extensions/forwarder.ts deleted file mode 100644 index ffa015dd2..000000000 --- a/contracts/core/test/extensions/forwarder.ts +++ /dev/null @@ -1,1288 +0,0 @@ -import { - chaiSetup, - constants, - ContractName, - ERC20BalancesByOwner, - expectContractCreationFailedAsync, - expectTransactionFailedAsync, - OrderFactory, - provider, - sendTransactionResult, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokenArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - WETH9Contract, -} from '@0x/contracts-tokens'; -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 { ExchangeContract } from '../../generated-wrappers/exchange'; -import { ForwarderContract } from '../../generated-wrappers/forwarder'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { ForwarderWrapper } from '../utils/forwarder_wrapper'; - -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 otherAddress: 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, otherAddress] = 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(tokenArtifacts.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( - artifacts.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( - artifacts.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/core/test/extensions/order_validator.ts b/contracts/core/test/extensions/order_validator.ts deleted file mode 100644 index 622710c98..000000000 --- a/contracts/core/test/extensions/order_validator.ts +++ /dev/null @@ -1,606 +0,0 @@ -import { - chaiSetup, - constants, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { DummyERC20TokenContract, DummyERC721TokenContract } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { OrderValidatorContract } from '../../generated-wrappers/order_validator'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('OrderValidator', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let erc20AssetData: string; - let erc721AssetData: string; - - let erc20Token: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let exchange: ExchangeContract; - let orderValidator: OrderValidatorContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - - let signedOrder: SignedOrder; - let signedOrder2: SignedOrder; - let orderFactory: OrderFactory; - - const tokenId = new BigNumber(123456789); - const tokenId2 = new BigNumber(987654321); - const ERC721_BALANCE = new BigNumber(1); - const ERC721_ALLOWANCE = new BigNumber(1); - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress] = _.slice(accounts, 0, 3)); - - const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 2; - [erc20Token, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - - const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - const exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - orderValidator = await OrderValidatorContract.deployFrom0xArtifactAsync( - artifacts.OrderValidator, - provider, - txDefaults, - exchange.address, - zrxAssetData, - ); - - erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); - erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId); - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress: constants.NULL_ADDRESS, - makerAssetData: erc20AssetData, - takerAssetData: erc721AssetData, - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('getBalanceAndAllowance', () => { - describe('getERC721TokenOwner', async () => { - it('should return the null address when tokenId is not owned', async () => { - const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(makerAddress, tokenId); - expect(tokenOwner).to.be.equal(constants.NULL_ADDRESS); - }); - it('should return the owner address when tokenId is owned', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(erc721Token.address, tokenId); - expect(tokenOwner).to.be.equal(makerAddress); - }); - }); - describe('ERC20 assetData', () => { - it('should return the correct balances and allowances when both values are 0', async () => { - const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc20AssetData, - ); - expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newBalance); - expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newAllowance); - }); - it('should return the correct balance and allowance when both values are non-zero', async () => { - const balance = new BigNumber(123); - const allowance = new BigNumber(456); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc20AssetData, - ); - expect(balance).to.be.bignumber.equal(newBalance); - expect(allowance).to.be.bignumber.equal(newAllowance); - }); - }); - describe('ERC721 assetData', () => { - it('should return a balance of 0 when the tokenId is not owned by target', async () => { - const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc721AssetData, - ); - expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return an allowance of 0 when no approval is set', async () => { - const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return a balance of 1 when the tokenId is owned by target', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc721AssetData, - ); - expect(newBalance).to.be.bignumber.equal(ERC721_BALANCE); - }); - it('should return an allowance of 1 when ERC721Proxy is approved for all', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - }); - it('should return an allowance of 0 when ERC721Proxy is approved for specific tokenId', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync( - makerAddress, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - }); - }); - describe('getBalancesAndAllowances', () => { - it('should return the correct balances and allowances when all values are 0', async () => { - const [ - [erc20Balance, erc721Balance], - [erc20Allowance, erc721Allowance], - ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [ - erc20AssetData, - erc721AssetData, - ]); - expect(erc20Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc721Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc20Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc721Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct balances and allowances when balances and allowances are non-zero', async () => { - const balance = new BigNumber(123); - const allowance = new BigNumber(456); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [ - [erc20Balance, erc721Balance], - [erc20Allowance, erc721Allowance], - ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [ - erc20AssetData, - erc721AssetData, - ]); - expect(erc20Balance).to.be.bignumber.equal(balance); - expect(erc721Balance).to.be.bignumber.equal(ERC721_BALANCE); - expect(erc20Allowance).to.be.bignumber.equal(allowance); - expect(erc721Allowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - }); - }); - describe('getTraderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct info when no balances or allowances are set', async () => { - const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress); - expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress); - expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); - describe('getTradersInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - signedOrder2 = await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2), - }); - }); - it('should return the correct info when no balances or allowances have been set', async () => { - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers); - - expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); - describe('getOrderAndTraderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct info when no balances or allowances are set', async () => { - const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync( - signedOrder, - takerAddress, - ); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync( - signedOrder, - takerAddress, - ); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); - describe('getOrdersAndTradersInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - signedOrder2 = await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2), - }); - }); - it('should return the correct info when no balances or allowances have been set', async () => { - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [ - [orderInfo1, orderInfo2], - [traderInfo1, traderInfo2], - ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers); - const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder); - const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); - expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1); - expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2); - expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [ - [orderInfo1, orderInfo2], - [traderInfo1, traderInfo2], - ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers); - const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder); - const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); - expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1); - expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE); - expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2); - expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); -}); -// tslint:disable:max-file-line-count diff --git a/contracts/core/test/tutorials/arbitrage.ts b/contracts/core/test/tutorials/arbitrage.ts deleted file mode 100644 index 78e0bc238..000000000 --- a/contracts/core/test/tutorials/arbitrage.ts +++ /dev/null @@ -1,260 +0,0 @@ -// import { ECSignature, SignedOrder, ZeroEx } from '0x.js'; -// import { BlockchainLifecycle, devConstants, web3Factory } from '@0x/dev-utils'; -// import { ExchangeContractErrs } from 'ethereum-types'; -// import { BigNumber } from '@0x/utils'; -// import { Web3Wrapper } from '@0x/web3-wrapper'; -// import * as chai from 'chai'; -// import ethUtil = require('ethereumjs-util'); -// import * as Web3 from 'web3'; - -// import { AccountLevelsContract } from '../../src/generated_contract_wrappers/account_levels'; -// import { ArbitrageContract } from '../../src/generated_contract_wrappers/arbitrage'; -// import { DummyTokenContract } from '../../src/generated_contract_wrappers/dummy_token'; -// import { EtherDeltaContract } from '../../src/generated_contract_wrappers/ether_delta'; -// import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; -// import { TokenTransferProxyContract } from '../../src/generated_contract_wrappers/token_transfer_proxy'; -// import { artifacts } from '../../util/artifacts'; -// import { Balances } from '../../util/balances'; -// import { constants } from '../../util/constants'; -// import { crypto } from '../../util/crypto'; -// import { ExchangeWrapper } from '../../util/exchange_wrapper'; -// import { OrderFactory } from '../../util/order_factory'; -// import { BalancesByOwner, ContractName } from '../../util/types'; -// import { chaiSetup } from '../utils/chai_setup'; - -// import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; - -// chaiSetup.configure(); -// const expect = chai.expect; -// const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -// describe('Arbitrage', () => { -// let coinbase: string; -// let maker: string; -// let edMaker: string; -// let edFrontRunner: string; -// let amountGet: BigNumber; -// let amountGive: BigNumber; -// let makerTokenAmount: BigNumber; -// let takerTokenAmount: BigNumber; -// const feeRecipient = constants.NULL_ADDRESS; -// const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); -// const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - -// let weth: DummyTokenContract; -// let zrx: DummyTokenContract; -// let arbitrage: ArbitrageContract; -// let etherDelta: EtherDeltaContract; - -// let signedOrder: SignedOrder; -// let exWrapper: ExchangeWrapper; -// let orderFactory: OrderFactory; - -// let zeroEx: ZeroEx; - -// // From a bird's eye view - we create two orders. -// // 0x order of 1 ZRX (maker) for 1 WETH (taker) -// // ED order of 2 WETH (tokenGive) for 1 ZRX (tokenGet) -// // And then we do an atomic arbitrage between them which gives us 1 WETH. -// before(async () => { -// const accounts = await web3Wrapper.getAvailableAddressesAsync(); -// [coinbase, maker, edMaker, edFrontRunner] = accounts; -// weth = await DummyTokenContract.deployFrom0xArtifactAsync( -// artifacts.DummyToken, -// provider, -// txDefaults, -// constants.DUMMY_TOKEN_NAME, -// constants.DUMMY_TOKEN_SYMBOL, -// constants.DUMMY_TOKEN_DECIMALS, -// constants.DUMMY_TOKEN_TOTAL_SUPPLY, -// ); -// zrx = await DummyTokenContract.deployFrom0xArtifactAsync( -// artifacts.DummyToken, -// provider, -// txDefaults, -// constants.DUMMY_TOKEN_NAME, -// constants.DUMMY_TOKEN_SYMBOL, -// constants.DUMMY_TOKEN_DECIMALS, -// constants.DUMMY_TOKEN_TOTAL_SUPPLY, -// ); -// const accountLevels = await AccountLevelsContract.deployFrom0xArtifactAsync( -// artifacts.AccountLevels, -// provider, -// txDefaults, -// ); -// const edAdminAddress = accounts[0]; -// const edMakerFee = new BigNumber(0); -// const edTakerFee = new BigNumber(0); -// const edFeeRebate = new BigNumber(0); -// etherDelta = await EtherDeltaContract.deployFrom0xArtifactAsync( -// artifacts.EtherDelta, -// provider, -// txDefaults, -// edAdminAddress, -// feeRecipient, -// accountLevels.address, -// edMakerFee, -// edTakerFee, -// edFeeRebate, -// ); -// const tokenTransferProxy = await TokenTransferProxyContract.deployFrom0xArtifactAsync( -// artifacts.TokenTransferProxy, -// provider, -// txDefaults, -// ); -// const exchange = await ExchangeContract.deployFrom0xArtifactAsync( -// artifacts.Exchange, -// provider, -// txDefaults, -// zrx.address, -// tokenTransferProxy.address, -// ); -// await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); -// zeroEx = new ZeroEx(provider, { -// exchangeContractAddress: exchange.address, -// networkId: constants.TESTRPC_NETWORK_ID, -// }); -// exWrapper = new ExchangeWrapper(exchange, provider); - -// makerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18); -// takerTokenAmount = makerTokenAmount; -// const defaultOrderParams = { -// exchangeContractAddress: exchange.address, -// maker, -// feeRecipient, -// makerTokenAddress: zrx.address, -// takerTokenAddress: weth.address, -// makerTokenAmount, -// takerTokenAmount, -// makerFee: new BigNumber(0), -// takerFee: new BigNumber(0), -// }; -// orderFactory = new OrderFactory(zeroEx, defaultOrderParams); -// arbitrage = await ArbitrageContract.deployFrom0xArtifactAsync( -// artifacts.Arbitrage, -// provider, -// txDefaults, -// exchange.address, -// etherDelta.address, -// tokenTransferProxy.address, -// ); -// // Enable arbitrage and withdrawals of tokens -// await arbitrage.setAllowances.sendTransactionAsync(weth.address, { from: coinbase }); -// await arbitrage.setAllowances.sendTransactionAsync(zrx.address, { from: coinbase }); - -// // Give some tokens to arbitrage contract -// await weth.setBalance.sendTransactionAsync(arbitrage.address, takerTokenAmount, { from: coinbase }); - -// // Fund the maker on exchange side -// await zrx.setBalance.sendTransactionAsync(maker, makerTokenAmount, { from: coinbase }); -// // Set the allowance for the maker on Exchange side -// await zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker }); - -// amountGive = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); -// // Fund the maker on EtherDelta side -// await weth.setBalance.sendTransactionAsync(edMaker, amountGive, { from: coinbase }); -// // Set the allowance for the maker on EtherDelta side -// await weth.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edMaker }); -// // Deposit maker funds into EtherDelta -// await etherDelta.depositToken.sendTransactionAsync(weth.address, amountGive, { from: edMaker }); - -// amountGet = makerTokenAmount; -// // Fund the front runner on EtherDelta side -// await zrx.setBalance.sendTransactionAsync(edFrontRunner, amountGet, { from: coinbase }); -// // Set the allowance for the front-runner on EtherDelta side -// await zrx.approve.sendTransactionAsync(etherDelta.address, INITIAL_ALLOWANCE, { from: edFrontRunner }); -// // Deposit front runner funds into EtherDelta -// await etherDelta.depositToken.sendTransactionAsync(zrx.address, amountGet, { from: edFrontRunner }); -// }); -// beforeEach(async () => { -// await blockchainLifecycle.startAsync(); -// }); -// afterEach(async () => { -// await blockchainLifecycle.revertAsync(); -// }); -// describe('makeAtomicTrade', () => { -// let addresses: string[]; -// let values: BigNumber[]; -// let v: number[]; -// let r: string[]; -// let s: string[]; -// let tokenGet: string; -// let tokenGive: string; -// let expires: BigNumber; -// let nonce: BigNumber; -// let edSignature: ECSignature; -// before(async () => { -// signedOrder = await orderFactory.newSignedOrderAsync(); -// tokenGet = zrx.address; -// tokenGive = weth.address; -// const blockNumber = await web3Wrapper.getBlockNumberAsync(); -// const ED_ORDER_EXPIRATION_IN_BLOCKS = 10; -// expires = new BigNumber(blockNumber + ED_ORDER_EXPIRATION_IN_BLOCKS); -// nonce = new BigNumber(42); -// const edOrderHash = `0x${crypto -// .solSHA256([etherDelta.address, tokenGet, amountGet, tokenGive, amountGive, expires, nonce]) -// .toString('hex')}`; -// const shouldAddPersonalMessagePrefix = false; -// edSignature = await zeroEx.signOrderHashAsync(edOrderHash, edMaker, shouldAddPersonalMessagePrefix); -// addresses = [ -// signedOrder.maker, -// signedOrder.taker, -// signedOrder.makerTokenAddress, -// signedOrder.takerTokenAddress, -// signedOrder.feeRecipient, -// edMaker, -// ]; -// const fillTakerTokenAmount = takerTokenAmount; -// const edFillAmount = makerTokenAmount; -// values = [ -// signedOrder.makerTokenAmount, -// signedOrder.takerTokenAmount, -// signedOrder.makerFee, -// signedOrder.takerFee, -// signedOrder.expirationUnixTimestampSec, -// signedOrder.salt, -// fillTakerTokenAmount, -// amountGet, -// amountGive, -// expires, -// nonce, -// edFillAmount, -// ]; -// v = [signedOrder.ecSignature.v, edSignature.v]; -// r = [signedOrder.ecSignature.r, edSignature.r]; -// s = [signedOrder.ecSignature.s, edSignature.s]; -// }); -// it('should successfully execute the arbitrage if not front-runned', async () => { -// const txHash = await arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { -// from: coinbase, -// }); -// const res = await zeroEx.awaitTransactionMinedAsync(txHash); -// const postBalance = await weth.balanceOf.callAsync(arbitrage.address); -// expect(postBalance).to.be.bignumber.equal(amountGive); -// }); -// it('should fail and revert if front-runned', async () => { -// const preBalance = await weth.balanceOf.callAsync(arbitrage.address); -// // Front-running transaction -// await etherDelta.trade.sendTransactionAsync( -// tokenGet, -// amountGet, -// tokenGive, -// amountGive, -// expires, -// nonce, -// edMaker, -// edSignature.v, -// edSignature.r, -// edSignature.s, -// amountGet, -// { from: edFrontRunner }, -// ); -// // tslint:disable-next-line:await-promise -// await expect( -// arbitrage.makeAtomicTrade.sendTransactionAsync(addresses, values, v, r, s, { from: coinbase }), -// ).to.be.rejectedWith(constants.REVERT); -// const postBalance = await weth.balanceOf.callAsync(arbitrage.address); -// expect(preBalance).to.be.bignumber.equal(postBalance); -// }); -// }); -// }); diff --git a/contracts/core/test/utils/forwarder_wrapper.ts b/contracts/core/test/utils/forwarder_wrapper.ts deleted file mode 100644 index 4c78ecd79..000000000 --- a/contracts/core/test/utils/forwarder_wrapper.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { constants, formatters, LogDecoder, MarketSellOrders } from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; -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) - .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, ...tokensArtifacts }); - } - 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/core/test/utils/index.ts b/contracts/core/test/utils/index.ts new file mode 100644 index 000000000..75cd88666 --- /dev/null +++ b/contracts/core/test/utils/index.ts @@ -0,0 +1,3 @@ +export * from './exchange_wrapper'; +export * from './erc20_wrapper'; +export * from './erc721_wrapper'; diff --git a/contracts/core/tsconfig.json b/contracts/core/tsconfig.json index d6adef2c5..db872fc32 100644 --- a/contracts/core/tsconfig.json +++ b/contracts/core/tsconfig.json @@ -8,14 +8,11 @@ "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ "./generated-artifacts/AssetProxyOwner.json", - "./generated-artifacts/DutchAuction.json", "./generated-artifacts/ERC20Proxy.json", "./generated-artifacts/ERC721Proxy.json", "./generated-artifacts/Exchange.json", - "./generated-artifacts/Forwarder.json", "./generated-artifacts/MixinAuthorizable.json", "./generated-artifacts/MultiAssetProxy.json", - "./generated-artifacts/OrderValidator.json", "./generated-artifacts/TestAssetProxyDispatcher.json", "./generated-artifacts/TestAssetProxyOwner.json", "./generated-artifacts/TestExchangeInternals.json", |