@0x/contracts-zero-ex: Introduce transformer contracts.
				
					
				
			This commit is contained in:
		
				
					committed by
					
						 Lawrence Forman
						Lawrence Forman
					
				
			
			
				
	
			
			
			
						parent
						
							0e1a5a375a
						
					
				
				
					commit
					2ba3818b65
				
			| @@ -98,6 +98,21 @@ library LibTransformERC20RichErrors { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // Common Transformer errors /////////////////////////////////////////////// | ||||
|  | ||||
|     function InvalidTransformDataError( | ||||
|         bytes memory transformData | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bytes memory) | ||||
|     { | ||||
|         return abi.encodeWithSelector( | ||||
|             bytes4(keccak256("InvalidTransformDataError(bytes)")), | ||||
|             transformData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // FillQuoteTransformer errors ///////////////////////////////////////////// | ||||
|  | ||||
|     function IncompleteFillSellQuoteError( | ||||
| @@ -177,24 +192,7 @@ library LibTransformERC20RichErrors { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // WethTransformer errors //////////////////////////////////////////////////// | ||||
|  | ||||
|     function WrongNumberOfTokensReceivedError( | ||||
|         uint256 actual, | ||||
|         uint256 expected | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bytes memory) | ||||
|     { | ||||
|         return abi.encodeWithSelector( | ||||
|             bytes4(keccak256("WrongNumberOfTokensReceivedError(uint256,uint256)")), | ||||
|             actual, | ||||
|             expected | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function InvalidTokenReceivedError( | ||||
|     function InvalidTakerFeeTokenError( | ||||
|         address token | ||||
|     ) | ||||
|         internal | ||||
| @@ -202,8 +200,9 @@ library LibTransformERC20RichErrors { | ||||
|         returns (bytes memory) | ||||
|     { | ||||
|         return abi.encodeWithSelector( | ||||
|             bytes4(keccak256("InvalidTokenReceivedError(address)")), | ||||
|             bytes4(keccak256("InvalidTakerFeeTokenError(address)")), | ||||
|             token | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,418 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "../errors/LibTransformERC20RichErrors.sol"; | ||||
| import "../vendor/v3/IExchange.sol"; | ||||
| import "./IERC20Transformer.sol"; | ||||
| import "./LibERC20Transformer.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev A transformer that fills an ERC20 market sell/buy quote. | ||||
| contract FillQuoteTransformer is | ||||
|     IERC20Transformer | ||||
| { | ||||
|     // solhint-disable indent,no-empty-blocks,no-unused-vars | ||||
|  | ||||
|     /// @dev Transform data to ABI-encode and pass into `transform()`. | ||||
|     struct TransformData { | ||||
|         // The token being sold. | ||||
|         // This should be an actual token, not the ETH pseudo-token. | ||||
|         IERC20TokenV06 sellToken; | ||||
|         // The token being bought. | ||||
|         // This should be an actual token, not the ETH pseudo-token. | ||||
|         IERC20TokenV06 buyToken; | ||||
|         // The orders to fill. | ||||
|         IExchange.Order[] orders; | ||||
|         // Signatures for each respective order in `orders`. | ||||
|         bytes[] signatures; | ||||
|         // Maximum fill amount for each order. This may be shorter than the | ||||
|         // number of orders, where missing entries will be treated as `uint256(-1)`. | ||||
|         // For sells, this will be the maximum sell amount (taker asset). | ||||
|         // For buys, this will be the maximum buy amount (maker asset). | ||||
|         uint256[] maxOrderFillAmounts; | ||||
|         // Amount of `sellToken` to sell. May be `uint256(-1)` to sell entire | ||||
|         // amount of `sellToken` received. Zero if performing a market buy. | ||||
|         uint256 sellAmount; | ||||
|         // Amount of `buyToken` to buy. Zero if performing a market sell. | ||||
|         uint256 buyAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev Results of a call to `_fillOrder()`. | ||||
|     struct FillOrderResults { | ||||
|         // The amount of taker tokens sold, according to balance checks. | ||||
|         uint256 takerTokenSoldAmount; | ||||
|         // The amount of maker tokens sold, according to balance checks. | ||||
|         uint256 makerTokenBoughtAmount; | ||||
|         // The amount of protocol fee paid. | ||||
|         uint256 protocolFeePaid; | ||||
|     } | ||||
|  | ||||
|     /// @dev The Exchange ERC20Proxy ID. | ||||
|     bytes4 constant private ERC20_ASSET_PROXY_ID = 0xf47261b0; | ||||
|  | ||||
|     /// @dev The Exchange contract. | ||||
|     IExchange public immutable exchange; | ||||
|     /// @dev The ERC20Proxy address. | ||||
|     address public immutable erc20Proxy; | ||||
|  | ||||
|     using LibERC20TokenV06 for IERC20TokenV06; | ||||
|     using LibERC20Transformer for IERC20TokenV06; | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     constructor(IExchange exchange_) public { | ||||
|         exchange = exchange_; | ||||
|         erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sell this contract's entire balance of of `sellToken` in exchange | ||||
|     ///      for `buyToken` by filling `orders`. Protocol fees should be attached | ||||
|     ///      to this call. `buyToken` and excess ETH will be transferred back to the caller. | ||||
|     ///      This function cannot be re-entered. | ||||
|     /// @param data_ ABI-encoded `TransformData`. | ||||
|     /// @return success `TRANSFORMER_SUCCESS` on success. | ||||
|     function transform( | ||||
|         bytes32, // callDataHash, | ||||
|         address payable, // taker, | ||||
|         bytes calldata data_ | ||||
|     ) | ||||
|         external | ||||
|         override | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         TransformData memory data = abi.decode(data_, (TransformData)); | ||||
|  | ||||
|         // Validate data fields. | ||||
|         if (data.sellToken.isTokenETH() || | ||||
|             data.buyToken.isTokenETH() || | ||||
|             data.orders.length != data.signatures.length) | ||||
|         { | ||||
|             LibTransformERC20RichErrors.InvalidTransformDataError(data_).rrevert(); | ||||
|         } | ||||
|  | ||||
|         // If `sellAmount == -1` and `buyAmount == 0` then we are selling | ||||
|         // the entire balance of `sellToken`. This is useful in cases where | ||||
|         // the exact sell amount is not exactly known in advance, like when | ||||
|         // unwrapping Chai/cUSDC/cDAI. | ||||
|         if (data.sellAmount == uint256(-1) && data.buyAmount == 0) { | ||||
|             data.sellAmount = data.sellToken.getTokenBalanceOf(address(this)); | ||||
|         } | ||||
|  | ||||
|         // Approve the ERC20 proxy to spend `sellToken`. | ||||
|         data.sellToken.approveIfBelow(erc20Proxy, data.sellAmount); | ||||
|  | ||||
|         // Fill the orders. | ||||
|         uint256 singleProtocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice); | ||||
|         uint256 ethRemaining = address(this).balance; | ||||
|         uint256 boughtAmount = 0; | ||||
|         uint256 soldAmount = 0; | ||||
|         for (uint256 i = 0; i < data.orders.length; ++i) { | ||||
|             // Check if we've hit our targets. | ||||
|             if (data.buyAmount == 0) { | ||||
|                 // Market sell check. | ||||
|                 if (soldAmount >= data.sellAmount) { | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|                 // Market buy check. | ||||
|                 if (boughtAmount >= data.buyAmount) { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Ensure we have enough ETH to cover the protocol fee. | ||||
|             if (ethRemaining < singleProtocolFee) { | ||||
|                 LibTransformERC20RichErrors | ||||
|                     .InsufficientProtocolFeeError(ethRemaining, singleProtocolFee) | ||||
|                     .rrevert(); | ||||
|             } | ||||
|  | ||||
|             // Fill the order. | ||||
|             FillOrderResults memory results; | ||||
|             if (data.buyAmount == 0) { | ||||
|                 // Market sell. | ||||
|                 results = _sellToOrder( | ||||
|                     data.buyToken, | ||||
|                     data.sellToken, | ||||
|                     data.orders[i], | ||||
|                     data.signatures[i], | ||||
|                     data.sellAmount.safeSub(soldAmount).min256( | ||||
|                         data.maxOrderFillAmounts.length > i | ||||
|                         ? data.maxOrderFillAmounts[i] | ||||
|                         : uint256(-1) | ||||
|                     ), | ||||
|                     singleProtocolFee | ||||
|                 ); | ||||
|             } else { | ||||
|                 // Market buy. | ||||
|                 results = _buyFromOrder( | ||||
|                     data.buyToken, | ||||
|                     data.sellToken, | ||||
|                     data.orders[i], | ||||
|                     data.signatures[i], | ||||
|                     data.buyAmount.safeSub(boughtAmount).min256( | ||||
|                         data.maxOrderFillAmounts.length > i | ||||
|                         ? data.maxOrderFillAmounts[i] | ||||
|                         : uint256(-1) | ||||
|                     ), | ||||
|                     singleProtocolFee | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             // Accumulate totals. | ||||
|             soldAmount = soldAmount.safeAdd(results.takerTokenSoldAmount); | ||||
|             boughtAmount = boughtAmount.safeAdd(results.makerTokenBoughtAmount); | ||||
|             ethRemaining = ethRemaining.safeSub(results.protocolFeePaid); | ||||
|         } | ||||
|  | ||||
|         // Ensure we hit our targets. | ||||
|         if (data.buyAmount == 0) { | ||||
|             // Market sell check. | ||||
|             if (soldAmount < data.sellAmount) { | ||||
|                 LibTransformERC20RichErrors | ||||
|                     .IncompleteFillSellQuoteError( | ||||
|                         address(data.sellToken), | ||||
|                         soldAmount, | ||||
|                         data.sellAmount | ||||
|                     ).rrevert(); | ||||
|             } | ||||
|         } else { | ||||
|             // Market buy check. | ||||
|             if (boughtAmount < data.buyAmount) { | ||||
|                 LibTransformERC20RichErrors | ||||
|                     .IncompleteFillBuyQuoteError( | ||||
|                         address(data.buyToken), | ||||
|                         boughtAmount, | ||||
|                         data.buyAmount | ||||
|                     ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|         return LibERC20Transformer.TRANSFORMER_SUCCESS; | ||||
|     } | ||||
|  | ||||
|     /// @dev Try to sell up to `sellAmount` from an order. | ||||
|     /// @param makerToken The maker/buy token. | ||||
|     /// @param takerToken The taker/sell token. | ||||
|     /// @param order The order to fill. | ||||
|     /// @param signature The signature for `order`. | ||||
|     /// @param sellAmount Amount of taker token to sell. | ||||
|     /// @param protocolFee The protocol fee needed to fill `order`. | ||||
|     function _sellToOrder( | ||||
|         IERC20TokenV06 makerToken, | ||||
|         IERC20TokenV06 takerToken, | ||||
|         IExchange.Order memory order, | ||||
|         bytes memory signature, | ||||
|         uint256 sellAmount, | ||||
|         uint256 protocolFee | ||||
|     ) | ||||
|         private | ||||
|         returns (FillOrderResults memory results) | ||||
|     { | ||||
|         IERC20TokenV06 takerFeeToken = order.takerFeeAssetData.length == 0 | ||||
|             ? IERC20TokenV06(address(0)) | ||||
|             : _getTokenFromERC20AssetData(order.takerFeeAssetData); | ||||
|  | ||||
|         uint256 takerTokenFillAmount = sellAmount; | ||||
|  | ||||
|         if (order.takerFee != 0) { | ||||
|             if (takerFeeToken == makerToken) { | ||||
|                 // Taker fee is payable in the maker token, so we need to | ||||
|                 // approve the proxy to spend the maker token. | ||||
|                 // It isn't worth computing the actual taker fee | ||||
|                 // since `approveIfBelow()` will set the allowance to infinite. We | ||||
|                 // just need a reasonable upper bound to avoid unnecessarily re-approving. | ||||
|                 takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee); | ||||
|             } else if (takerFeeToken == takerToken){ | ||||
|                 // Taker fee is payable in the taker token, so we need to | ||||
|                 // reduce the fill amount to cover the fee. | ||||
|                 // takerTokenFillAmount' = | ||||
|                 //   (takerTokenFillAmount * order.takerAssetAmount) / | ||||
|                 //   (order.takerAssetAmount + order.takerFee) | ||||
|                 takerTokenFillAmount = LibMathV06.getPartialAmountCeil( | ||||
|                     order.takerAssetAmount, | ||||
|                     order.takerAssetAmount.safeAdd(order.takerFee), | ||||
|                     takerTokenFillAmount | ||||
|                 ); | ||||
|             } else { | ||||
|                 //  Only support taker or maker asset denominated taker fees. | ||||
|                 LibTransformERC20RichErrors.InvalidTakerFeeTokenError( | ||||
|                     address(takerFeeToken) | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Clamp fill amount to order size. | ||||
|         takerTokenFillAmount = LibSafeMathV06.min256( | ||||
|             takerTokenFillAmount, | ||||
|             order.takerAssetAmount | ||||
|         ); | ||||
|  | ||||
|         // Perform the fill. | ||||
|         return _fillOrder( | ||||
|             order, | ||||
|             signature, | ||||
|             takerTokenFillAmount, | ||||
|             protocolFee, | ||||
|             makerToken, | ||||
|             takerFeeToken == takerToken | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Try to buy up to `buyAmount` from an order. | ||||
|     /// @param makerToken The maker/buy token. | ||||
|     /// @param takerToken The taker/sell token. | ||||
|     /// @param order The order to fill. | ||||
|     /// @param signature The signature for `order`. | ||||
|     /// @param buyAmount Amount of maker token to buy. | ||||
|     /// @param protocolFee The protocol fee needed to fill `order`. | ||||
|     function _buyFromOrder( | ||||
|         IERC20TokenV06 makerToken, | ||||
|         IERC20TokenV06 takerToken, | ||||
|         IExchange.Order memory order, | ||||
|         bytes memory signature, | ||||
|         uint256 buyAmount, | ||||
|         uint256 protocolFee | ||||
|     ) | ||||
|         private | ||||
|         returns (FillOrderResults memory results) | ||||
|     { | ||||
|         IERC20TokenV06 takerFeeToken = order.takerFeeAssetData.length == 0 | ||||
|             ? IERC20TokenV06(address(0)) | ||||
|             : _getTokenFromERC20AssetData(order.takerFeeAssetData); | ||||
|  | ||||
|         uint256 makerTokenFillAmount = buyAmount; | ||||
|  | ||||
|         if (order.takerFee != 0) { | ||||
|             if (takerFeeToken == makerToken) { | ||||
|                 // Taker fee is payable in the maker token. | ||||
|                 // Increase the fill amount to account for maker tokens being | ||||
|                 // lost to the taker fee. | ||||
|                 // makerTokenFillAmount' = | ||||
|                 //  (order.makerAssetAmount * makerTokenFillAmount) / | ||||
|                 //  (order.makerAssetAmount - order.takerFee) | ||||
|                 makerTokenFillAmount = LibMathV06.getPartialAmountCeil( | ||||
|                     order.makerAssetAmount, | ||||
|                     order.makerAssetAmount.safeSub(order.takerFee), | ||||
|                     makerTokenFillAmount | ||||
|                 ); | ||||
|                 // Approve the proxy to spend the maker token. | ||||
|                 // It isn't worth computing the actual taker fee | ||||
|                 // since `approveIfBelow()` will set the allowance to infinite. We | ||||
|                 // just need a reasonable upper bound to avoid unnecessarily re-approving. | ||||
|                 takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee); | ||||
|             } else if (takerFeeToken != takerToken) { | ||||
|                 //  Only support taker or maker asset denominated taker fees. | ||||
|                 LibTransformERC20RichErrors.InvalidTakerFeeTokenError( | ||||
|                     address(takerFeeToken) | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Convert maker fill amount to taker fill amount. | ||||
|         uint256 takerTokenFillAmount = LibSafeMathV06.min256( | ||||
|             order.takerAssetAmount, | ||||
|             LibMathV06.getPartialAmountCeil( | ||||
|                 makerTokenFillAmount, | ||||
|                 order.makerAssetAmount, | ||||
|                 order.takerAssetAmount | ||||
|             ) | ||||
|         ); | ||||
|  | ||||
|         // Perform the fill. | ||||
|         return _fillOrder( | ||||
|             order, | ||||
|             signature, | ||||
|             takerTokenFillAmount, | ||||
|             protocolFee, | ||||
|             makerToken, | ||||
|             takerFeeToken == takerToken | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Attempt to fill an order. If the fill reverts, the revert will be | ||||
|     ///      swallowed and `results` will be zeroed out. | ||||
|     /// @param order The order to fill. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerAssetFillAmount How much taker asset to fill. | ||||
|     /// @param protocolFee The protocol fee needed to fill this order. | ||||
|     /// @param makerToken The maker token. | ||||
|     /// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the | ||||
|     ///        taker token. | ||||
|     function _fillOrder( | ||||
|         IExchange.Order memory order, | ||||
|         bytes memory signature, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         uint256 protocolFee, | ||||
|         IERC20TokenV06 makerToken, | ||||
|         bool isTakerFeeInTakerToken | ||||
|     ) | ||||
|         private | ||||
|         returns (FillOrderResults memory results) | ||||
|     { | ||||
|         // Track changes in the maker token balance. | ||||
|         results.makerTokenBoughtAmount = makerToken.balanceOf(address(this)); | ||||
|         try | ||||
|             exchange.fillOrder | ||||
|                 {value: protocolFee} | ||||
|                 (order, takerAssetFillAmount, signature) | ||||
|             returns (IExchange.FillResults memory fillResults) | ||||
|         { | ||||
|             // Update maker quantity based on changes in token balances. | ||||
|             results.makerTokenBoughtAmount = makerToken.balanceOf(address(this)) | ||||
|                 .safeSub(results.makerTokenBoughtAmount); | ||||
|             // We can trust the other fill result quantities. | ||||
|             results.protocolFeePaid = fillResults.protocolFeePaid; | ||||
|             results.takerTokenSoldAmount = fillResults.takerAssetFilledAmount; | ||||
|             // If the taker fee is payable in the taker asset, include the | ||||
|             // taker fee in the total amount sold. | ||||
|             if (isTakerFeeInTakerToken) { | ||||
|                 results.takerTokenSoldAmount = | ||||
|                     results.takerTokenSoldAmount.safeAdd(fillResults.takerFeePaid); | ||||
|             } | ||||
|         } catch (bytes memory) { | ||||
|             // If the fill fails, zero out fill quantities. | ||||
|             results.makerTokenBoughtAmount = 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Extract the token from plain ERC20 asset data. | ||||
|     /// @param assetData The order asset data. | ||||
|     function _getTokenFromERC20AssetData(bytes memory assetData) | ||||
|         private | ||||
|         pure | ||||
|         returns (IERC20TokenV06 token) | ||||
|     { | ||||
|         if (assetData.length != 36 || | ||||
|             LibBytesV06.readBytes4(assetData, 0) != ERC20_ASSET_PROXY_ID) | ||||
|         { | ||||
|             LibTransformERC20RichErrors | ||||
|                 .InvalidERC20AssetDataError(assetData) | ||||
|                 .rrevert(); | ||||
|         } | ||||
|         return IERC20TokenV06(LibBytesV06.readAddress(assetData, 16)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,77 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; | ||||
| import "../errors/LibTransformERC20RichErrors.sol"; | ||||
| import "./IERC20Transformer.sol"; | ||||
| import "./LibERC20Transformer.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev A transformer that transfers tokens to the taker. | ||||
| contract PayTakerTransformer is | ||||
|     IERC20Transformer | ||||
| { | ||||
|     /// @dev Transform data to ABI-encode and pass into `transform()`. | ||||
|     struct TransformData { | ||||
|         // The tokens to transfer to the taker. | ||||
|         IERC20TokenV06[] tokens; | ||||
|         // Amount of each token in `tokens` to transfer to the taker. | ||||
|         // `uint(-1)` will transfer the entire balance. | ||||
|         uint256[] amounts; | ||||
|     } | ||||
|  | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibERC20Transformer for IERC20TokenV06; | ||||
|  | ||||
|     /// @dev Forwards tokens to the taker. | ||||
|     /// @param taker The taker address (caller of `TransformERC20.transformERC20()`). | ||||
|     /// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer. | ||||
|     /// @return success `TRANSFORMER_SUCCESS` on success. | ||||
|     function transform( | ||||
|         bytes32, // callDataHash, | ||||
|         address payable taker, | ||||
|         bytes calldata data_ | ||||
|     ) | ||||
|         external | ||||
|         override | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         TransformData memory data = abi.decode(data_, (TransformData)); | ||||
|  | ||||
|         // Transfer tokens directly to the taker. | ||||
|         for (uint256 i = 0; i < data.tokens.length; ++i) { | ||||
|             // The `amounts` array can be shorter than the `tokens` array. | ||||
|             // Missing elements are treated as `uint256(-1)`. | ||||
|             uint256 amount = data.amounts.length > i ? data.amounts[i] : uint256(-1); | ||||
|             if (amount == uint256(-1)) { | ||||
|                 amount = data.tokens[i].getTokenBalanceOf(address(this)); | ||||
|             } | ||||
|             if (amount != 0) { | ||||
|                 data.tokens[i].transformerTransfer(taker, amount); | ||||
|             } | ||||
|         } | ||||
|         return LibERC20Transformer.TRANSFORMER_SUCCESS; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,91 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "../errors/LibTransformERC20RichErrors.sol"; | ||||
| import "./IERC20Transformer.sol"; | ||||
| import "./LibERC20Transformer.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev A transformer that wraps or unwraps WETH. | ||||
| contract WethTransformer is | ||||
|     IERC20Transformer | ||||
| { | ||||
|     /// @dev Transform data to ABI-encode and pass into `transform()`. | ||||
|     struct TransformData { | ||||
|         // The token to wrap/unwrap. Must be either ETH or WETH. | ||||
|         IERC20TokenV06 token; | ||||
|         // Amount of `token` to wrap or unwrap. | ||||
|         // `uint(-1)` will unwrap the entire balance. | ||||
|         uint256 amount; | ||||
|     } | ||||
|  | ||||
|     // solhint-disable | ||||
|     /// @dev The WETH contract address. | ||||
|     IEtherTokenV06 public immutable weth; | ||||
|     // solhint-enable | ||||
|  | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibERC20Transformer for IERC20TokenV06; | ||||
|  | ||||
|     /// @dev Construct the transformer and store the WETH address in an immutable. | ||||
|     /// @param weth_ The weth token. | ||||
|     constructor(IEtherTokenV06 weth_) public { | ||||
|         weth = weth_; | ||||
|     } | ||||
|  | ||||
|     /// @dev Wraps and unwraps WETH. | ||||
|     /// @param data_ ABI-encoded `TransformData`, indicating which token to wrap/umwrap. | ||||
|     /// @return success `TRANSFORMER_SUCCESS` on success. | ||||
|     function transform( | ||||
|         bytes32, // callDataHash, | ||||
|         address payable, // taker, | ||||
|         bytes calldata data_ | ||||
|     ) | ||||
|         external | ||||
|         override | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         TransformData memory data = abi.decode(data_, (TransformData)); | ||||
|         if (!data.token.isTokenETH() && data.token != weth) { | ||||
|             LibTransformERC20RichErrors.InvalidTransformDataError(data_).rrevert(); | ||||
|         } | ||||
|  | ||||
|         uint256 amount = data.amount; | ||||
|         if (amount == uint256(-1)) { | ||||
|             amount = data.token.getTokenBalanceOf(address(this)); | ||||
|         } | ||||
|  | ||||
|         if (amount != 0) { | ||||
|             if (data.token.isTokenETH()) { | ||||
|                 // Wrap ETH. | ||||
|                 weth.deposit{value: amount}(); | ||||
|             } else { | ||||
|                 // Unwrap WETH. | ||||
|                 weth.withdraw(amount); | ||||
|             } | ||||
|         } | ||||
|         return LibERC20Transformer.TRANSFORMER_SUCCESS; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										107
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IExchange.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IExchange.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
|  | ||||
| /// @dev Interface to the V3 Exchange. | ||||
| interface IExchange { | ||||
|  | ||||
|     /// @dev V3 Order structure. | ||||
|     struct Order { | ||||
|         // Address that created the order. | ||||
|         address makerAddress; | ||||
|         // Address that is allowed to fill the order. | ||||
|         // If set to 0, any address is allowed to fill the order. | ||||
|         address takerAddress; | ||||
|         // Address that will recieve fees when order is filled. | ||||
|         address feeRecipientAddress; | ||||
|         // Address that is allowed to call Exchange contract methods that affect this order. | ||||
|         // If set to 0, any address is allowed to call these methods. | ||||
|         address senderAddress; | ||||
|         // Amount of makerAsset being offered by maker. Must be greater than 0. | ||||
|         uint256 makerAssetAmount; | ||||
|         // Amount of takerAsset being bid on by maker. Must be greater than 0. | ||||
|         uint256 takerAssetAmount; | ||||
|         // Fee paid to feeRecipient by maker when order is filled. | ||||
|         uint256 makerFee; | ||||
|         // Fee paid to feeRecipient by taker when order is filled. | ||||
|         uint256 takerFee; | ||||
|         // Timestamp in seconds at which order expires. | ||||
|         uint256 expirationTimeSeconds; | ||||
|         // Arbitrary number to facilitate uniqueness of the order's hash. | ||||
|         uint256 salt; | ||||
|         // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. | ||||
|         // The leading bytes4 references the id of the asset proxy. | ||||
|         bytes makerAssetData; | ||||
|         // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. | ||||
|         // The leading bytes4 references the id of the asset proxy. | ||||
|         bytes takerAssetData; | ||||
|         // Encoded data that can be decoded by a specified proxy contract when transferring makerFeeAsset. | ||||
|         // The leading bytes4 references the id of the asset proxy. | ||||
|         bytes makerFeeAssetData; | ||||
|         // Encoded data that can be decoded by a specified proxy contract when transferring takerFeeAsset. | ||||
|         // The leading bytes4 references the id of the asset proxy. | ||||
|         bytes takerFeeAssetData; | ||||
|     } | ||||
|  | ||||
|     /// @dev V3 `fillOrder()` results.` | ||||
|     struct FillResults { | ||||
|         // Total amount of makerAsset(s) filled. | ||||
|         uint256 makerAssetFilledAmount; | ||||
|         // Total amount of takerAsset(s) filled. | ||||
|         uint256 takerAssetFilledAmount; | ||||
|         // Total amount of fees paid by maker(s) to feeRecipient(s). | ||||
|         uint256 makerFeePaid; | ||||
|         // Total amount of fees paid by taker to feeRecipients(s). | ||||
|         uint256 takerFeePaid; | ||||
|         // Total amount of fees paid by taker to the staking contract. | ||||
|         uint256 protocolFeePaid; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills the input order. | ||||
|     /// @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 fillResults Amounts filled and fees paid by maker and taker. | ||||
|     function fillOrder( | ||||
|         Order calldata order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes calldata signature | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns (FillResults memory fillResults); | ||||
|  | ||||
|     /// @dev Returns the protocolFeeMultiplier | ||||
|     /// @return multiplier The multiplier for protocol fees. | ||||
|     function protocolFeeMultiplier() | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 multiplier); | ||||
|  | ||||
|     /// @dev Gets an asset proxy. | ||||
|     /// @param assetProxyId Id of the asset proxy. | ||||
|     /// @return proxyAddress The asset proxy registered to assetProxyId. | ||||
|     ///         Returns 0x0 if no proxy is registered. | ||||
|     function getAssetProxy(bytes4 assetProxyId) | ||||
|         external | ||||
|         view | ||||
|         returns (address proxyAddress); | ||||
| } | ||||
| @@ -0,0 +1,145 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "../src/vendor/v3/IExchange.sol"; | ||||
| import "./TestMintableERC20Token.sol"; | ||||
|  | ||||
|  | ||||
| contract TestFillQuoteTransformerExchange { | ||||
|  | ||||
|     struct FillBehavior { | ||||
|         // How much of the order is filled, in taker asset amount. | ||||
|         uint256 filledTakerAssetAmount; | ||||
|         // Scaling for maker assets minted, in 1e18. | ||||
|         uint256 makerAssetMintRatio; | ||||
|     } | ||||
|  | ||||
|     uint256 private constant PROTOCOL_FEE_MULTIPLIER = 1337; | ||||
|  | ||||
|     using LibSafeMathV06 for uint256; | ||||
|  | ||||
|     function fillOrder( | ||||
|         IExchange.Order calldata order, | ||||
|         uint256 takerAssetFillAmount, | ||||
|         bytes calldata signature | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns (IExchange.FillResults memory fillResults) | ||||
|     { | ||||
|         require( | ||||
|             signature.length != 0, | ||||
|             "TestFillQuoteTransformerExchange/INVALID_SIGNATURE" | ||||
|         ); | ||||
|         // The signature is the ABI-encoded FillBehavior data. | ||||
|         FillBehavior memory behavior = abi.decode(signature, (FillBehavior)); | ||||
|  | ||||
|         uint256 protocolFee = PROTOCOL_FEE_MULTIPLIER * tx.gasprice; | ||||
|         require( | ||||
|             msg.value == protocolFee, | ||||
|             "TestFillQuoteTransformerExchange/INSUFFICIENT_PROTOCOL_FEE" | ||||
|         ); | ||||
|         // Return excess protocol fee. | ||||
|         msg.sender.transfer(msg.value - protocolFee); | ||||
|  | ||||
|         // Take taker tokens. | ||||
|         TestMintableERC20Token takerToken = _getTokenFromAssetData(order.takerAssetData); | ||||
|         takerAssetFillAmount = LibSafeMathV06.min256( | ||||
|             order.takerAssetAmount.safeSub(behavior.filledTakerAssetAmount), | ||||
|             takerAssetFillAmount | ||||
|         ); | ||||
|         require( | ||||
|             takerToken.getSpendableAmount(msg.sender, address(this)) >= takerAssetFillAmount, | ||||
|             "TestFillQuoteTransformerExchange/INSUFFICIENT_TAKER_FUNDS" | ||||
|         ); | ||||
|         takerToken.transferFrom(msg.sender, order.makerAddress, takerAssetFillAmount); | ||||
|  | ||||
|         // Mint maker tokens. | ||||
|         uint256 makerAssetFilledAmount = LibMathV06.getPartialAmountFloor( | ||||
|             takerAssetFillAmount, | ||||
|             order.takerAssetAmount, | ||||
|             order.makerAssetAmount | ||||
|         ); | ||||
|         TestMintableERC20Token makerToken = _getTokenFromAssetData(order.makerAssetData); | ||||
|         makerToken.mint( | ||||
|             msg.sender, | ||||
|             LibMathV06.getPartialAmountFloor( | ||||
|                 behavior.makerAssetMintRatio, | ||||
|                 1e18, | ||||
|                 makerAssetFilledAmount | ||||
|             ) | ||||
|         ); | ||||
|  | ||||
|         // Take taker fee. | ||||
|         TestMintableERC20Token takerFeeToken = _getTokenFromAssetData(order.takerFeeAssetData); | ||||
|         uint256 takerFee = LibMathV06.getPartialAmountFloor( | ||||
|             takerAssetFillAmount, | ||||
|             order.takerAssetAmount, | ||||
|             order.takerFee | ||||
|         ); | ||||
|         require( | ||||
|             takerFeeToken.getSpendableAmount(msg.sender, address(this)) >= takerFee, | ||||
|             "TestFillQuoteTransformerExchange/INSUFFICIENT_TAKER_FEE_FUNDS" | ||||
|         ); | ||||
|         takerFeeToken.transferFrom(msg.sender, order.feeRecipientAddress, takerFee); | ||||
|  | ||||
|         fillResults.makerAssetFilledAmount = makerAssetFilledAmount; | ||||
|         fillResults.takerAssetFilledAmount = takerAssetFillAmount; | ||||
|         fillResults.makerFeePaid = uint256(-1); | ||||
|         fillResults.takerFeePaid = takerFee; | ||||
|         fillResults.protocolFeePaid = protocolFee; | ||||
|     } | ||||
|  | ||||
|     function encodeBehaviorData(FillBehavior calldata behavior) | ||||
|         external | ||||
|         pure | ||||
|         returns (bytes memory encoded) | ||||
|     { | ||||
|         return abi.encode(behavior); | ||||
|     } | ||||
|  | ||||
|     function protocolFeeMultiplier() | ||||
|         external | ||||
|         pure | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return PROTOCOL_FEE_MULTIPLIER; | ||||
|     } | ||||
|  | ||||
|     function getAssetProxy(bytes4) | ||||
|         external | ||||
|         view | ||||
|         returns (address) | ||||
|     { | ||||
|         return address(this); | ||||
|     } | ||||
|  | ||||
|     function _getTokenFromAssetData(bytes memory assetData) | ||||
|         private | ||||
|         pure | ||||
|         returns (TestMintableERC20Token token) | ||||
|     { | ||||
|         return TestMintableERC20Token(LibBytesV06.readAddress(assetData, 16)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/transformers/IERC20Transformer.sol"; | ||||
| import "./TestMintableERC20Token.sol"; | ||||
| import "./TestTransformerHost.sol"; | ||||
|  | ||||
|  | ||||
| contract TestFillQuoteTransformerHost is | ||||
|     TestTransformerHost | ||||
| { | ||||
|     function executeTransform( | ||||
|         IERC20Transformer transformer, | ||||
|         TestMintableERC20Token inputToken, | ||||
|         uint256 inputTokenAmount, | ||||
|         bytes calldata data | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|     { | ||||
|         if (inputTokenAmount != 0) { | ||||
|             inputToken.mint(address(this), inputTokenAmount); | ||||
|         } | ||||
|         // Have to make this call externally because transformers aren't payable. | ||||
|         this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data); | ||||
|     } | ||||
| } | ||||
| @@ -74,4 +74,14 @@ contract TestMintableERC20Token { | ||||
|         balanceOf[to] += amount; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function getSpendableAmount(address owner, address spender) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return balanceOf[owner] < allowance[owner][spender] | ||||
|             ? balanceOf[owner] | ||||
|             : allowance[owner][spender]; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										60
									
								
								contracts/zero-ex/contracts/test/TestTransformerHost.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								contracts/zero-ex/contracts/test/TestTransformerHost.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "../src/transformers/IERC20Transformer.sol"; | ||||
| import "../src/transformers/LibERC20Transformer.sol"; | ||||
|  | ||||
|  | ||||
| contract TestTransformerHost { | ||||
|  | ||||
|     using LibERC20Transformer for IERC20TokenV06; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     function rawExecuteTransform( | ||||
|         IERC20Transformer transformer, | ||||
|         bytes32 callDataHash, | ||||
|         address taker, | ||||
|         bytes calldata data | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         (bool success, bytes memory resultData) = | ||||
|             address(transformer).delegatecall(abi.encodeWithSelector( | ||||
|                 transformer.transform.selector, | ||||
|                 callDataHash, | ||||
|                 taker, | ||||
|                 data | ||||
|             )); | ||||
|         if (!success) { | ||||
|             resultData.rrevert(); | ||||
|         } | ||||
|         require( | ||||
|             abi.decode(resultData, (bytes4)) == LibERC20Transformer.TRANSFORMER_SUCCESS, | ||||
|             "TestFillQuoteTransformerTaker/UNSUCCESSFUL_RESULT" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // solhint-disable | ||||
|     receive() external payable {} | ||||
|     // solhint-enable | ||||
| } | ||||
							
								
								
									
										42
									
								
								contracts/zero-ex/contracts/test/TestWeth.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								contracts/zero-ex/contracts/test/TestWeth.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./TestMintableERC20Token.sol"; | ||||
|  | ||||
|  | ||||
| contract TestWeth is | ||||
|     TestMintableERC20Token | ||||
| { | ||||
|     function deposit() | ||||
|         external | ||||
|         payable | ||||
|     { | ||||
|         this.mint(msg.sender, msg.value); | ||||
|     } | ||||
|  | ||||
|     function withdraw(uint256 amount) | ||||
|         external | ||||
|     { | ||||
|         require(balanceOf[msg.sender] >= amount, "TestWeth/INSUFFICIENT_FUNDS"); | ||||
|         balanceOf[msg.sender] -= amount; | ||||
|         msg.sender.transfer(amount); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										53
									
								
								contracts/zero-ex/contracts/test/TestWethTransformerHost.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								contracts/zero-ex/contracts/test/TestWethTransformerHost.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/transformers/IERC20Transformer.sol"; | ||||
| import "./TestMintableERC20Token.sol"; | ||||
| import "./TestTransformerHost.sol"; | ||||
| import "./TestWeth.sol"; | ||||
|  | ||||
|  | ||||
| contract TestWethTransformerHost is | ||||
|     TestTransformerHost | ||||
| { | ||||
|     // solhint-disable | ||||
|     TestWeth private immutable _weth; | ||||
|     // solhint-enable | ||||
|  | ||||
|     constructor(TestWeth weth) public { | ||||
|         _weth = weth; | ||||
|     } | ||||
|  | ||||
|     function executeTransform( | ||||
|         uint256 wethAmount, | ||||
|         IERC20Transformer transformer, | ||||
|         bytes calldata data | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|     { | ||||
|         if (wethAmount != 0) { | ||||
|             _weth.deposit{value: wethAmount}(); | ||||
|         } | ||||
|         // Have to make this call externally because transformers aren't payable. | ||||
|         this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data); | ||||
|     } | ||||
| } | ||||
| @@ -38,7 +38,7 @@ | ||||
|         "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" | ||||
|     }, | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20", | ||||
|         "publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", | ||||
|         "abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|SimpleFunctionRegistry|TestCallTarget|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestZeroExFeature|TokenSpender|TransformERC20|ZeroEx).json" | ||||
|     }, | ||||
| @@ -56,6 +56,7 @@ | ||||
|         "@0x/contracts-gen": "^2.0.8", | ||||
|         "@0x/contracts-test-utils": "^5.3.2", | ||||
|         "@0x/dev-utils": "^3.2.1", | ||||
|         "@0x/order-utils": "^10.2.4", | ||||
|         "@0x/sol-compiler": "^4.0.8", | ||||
|         "@0x/subproviders": "^6.0.8", | ||||
|         "@0x/ts-doc-gen": "^0.0.22", | ||||
|   | ||||
| @@ -5,25 +5,31 @@ | ||||
|  */ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as FillQuoteTransformer from '../generated-artifacts/FillQuoteTransformer.json'; | ||||
| import * as FullMigration from '../generated-artifacts/FullMigration.json'; | ||||
| import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json'; | ||||
| import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json'; | ||||
| import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json'; | ||||
| import * as InitialMigration from '../generated-artifacts/InitialMigration.json'; | ||||
| import * as IOwnable from '../generated-artifacts/IOwnable.json'; | ||||
| import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json'; | ||||
| import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json'; | ||||
| import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json'; | ||||
| import * as LibERC20Transformer from '../generated-artifacts/LibERC20Transformer.json'; | ||||
| import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json'; | ||||
| import * as Puppet from '../generated-artifacts/Puppet.json'; | ||||
| import * as WethTransformer from '../generated-artifacts/WethTransformer.json'; | ||||
| import * as ZeroEx from '../generated-artifacts/ZeroEx.json'; | ||||
| export const artifacts = { | ||||
|     ZeroEx: ZeroEx as ContractArtifact, | ||||
|     FullMigration: FullMigration as ContractArtifact, | ||||
|     InitialMigration: InitialMigration as ContractArtifact, | ||||
|     IFlashWallet: IFlashWallet as ContractArtifact, | ||||
|     IAllowanceTarget: IAllowanceTarget as ContractArtifact, | ||||
|     Puppet: Puppet as ContractArtifact, | ||||
|     IERC20Transformer: IERC20Transformer as ContractArtifact, | ||||
|     IOwnable: IOwnable as ContractArtifact, | ||||
|     ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact, | ||||
|     ITokenSpender: ITokenSpender as ContractArtifact, | ||||
|     ITransformERC20: ITransformERC20 as ContractArtifact, | ||||
|     LibERC20Transformer: LibERC20Transformer as ContractArtifact, | ||||
|     PayTakerTransformer: PayTakerTransformer as ContractArtifact, | ||||
|     WethTransformer: WethTransformer as ContractArtifact, | ||||
|     FillQuoteTransformer: FillQuoteTransformer as ContractArtifact, | ||||
| }; | ||||
|   | ||||
							
								
								
									
										4
									
								
								contracts/zero-ex/src/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								contracts/zero-ex/src/constants.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| /* | ||||
|  * The pseudo-token address for ETH used by `tranformERC20()`. | ||||
|  */ | ||||
| export const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; | ||||
| @@ -1,9 +1,14 @@ | ||||
| export { artifacts } from './artifacts'; | ||||
| export { | ||||
|     FillQuoteTransformerContract, | ||||
|     IOwnableContract, | ||||
|     IOwnableEvents, | ||||
|     ISimpleFunctionRegistryContract, | ||||
|     ISimpleFunctionRegistryEvents, | ||||
|     ITokenSpenderContract, | ||||
|     ITransformERC20Contract, | ||||
|     PayTakerTransformerContract, | ||||
|     WethTransformerContract, | ||||
|     ZeroExContract, | ||||
| } from './wrappers'; | ||||
| export { ZeroExRevertErrors } from '@0x/utils'; | ||||
| @@ -36,4 +41,6 @@ export { | ||||
|     TupleDataItem, | ||||
|     StateMutability, | ||||
| } from 'ethereum-types'; | ||||
| export { rlpEncodeNonce } from './nonce_utils'; | ||||
|  | ||||
| export * from './constants'; | ||||
| export * from './transformer_data_encoders'; | ||||
|   | ||||
							
								
								
									
										114
									
								
								contracts/zero-ex/src/transformer_data_encoders.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								contracts/zero-ex/src/transformer_data_encoders.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| import { Order } from '@0x/types'; | ||||
| import { AbiEncoder, BigNumber } from '@0x/utils'; | ||||
|  | ||||
| const ORDER_ABI_COMPONENTS = [ | ||||
|     { name: 'makerAddress', type: 'address' }, | ||||
|     { name: 'takerAddress', type: 'address' }, | ||||
|     { name: 'feeRecipientAddress', type: 'address' }, | ||||
|     { name: 'senderAddress', type: 'address' }, | ||||
|     { name: 'makerAssetAmount', type: 'uint256' }, | ||||
|     { name: 'takerAssetAmount', type: 'uint256' }, | ||||
|     { name: 'makerFee', type: 'uint256' }, | ||||
|     { name: 'takerFee', type: 'uint256' }, | ||||
|     { name: 'expirationTimeSeconds', type: 'uint256' }, | ||||
|     { name: 'salt', type: 'uint256' }, | ||||
|     { name: 'makerAssetData', type: 'bytes' }, | ||||
|     { name: 'takerAssetData', type: 'bytes' }, | ||||
|     { name: 'makerFeeAssetData', type: 'bytes' }, | ||||
|     { name: 'takerFeeAssetData', type: 'bytes' }, | ||||
| ]; | ||||
|  | ||||
| /** | ||||
|  * ABI encoder for `FillQuoteTransformer.TransformData` | ||||
|  */ | ||||
| export const fillQuoteTransformerDataEncoder = AbiEncoder.create([ | ||||
|     { | ||||
|         name: 'data', | ||||
|         type: 'tuple', | ||||
|         components: [ | ||||
|             { name: 'sellToken', type: 'address' }, | ||||
|             { name: 'buyToken', type: 'address' }, | ||||
|             { | ||||
|                 name: 'orders', | ||||
|                 type: 'tuple[]', | ||||
|                 components: ORDER_ABI_COMPONENTS, | ||||
|             }, | ||||
|             { name: 'signatures', type: 'bytes[]' }, | ||||
|             { name: 'maxOrderFillAmounts', type: 'uint256[]' }, | ||||
|             { name: 'sellAmount', type: 'uint256' }, | ||||
|             { name: 'buyAmount', type: 'uint256' }, | ||||
|         ], | ||||
|     }, | ||||
| ]); | ||||
|  | ||||
| /** | ||||
|  * `FillQuoteTransformer.TransformData` | ||||
|  */ | ||||
| export interface FillQuoteTransformerData { | ||||
|     sellToken: string; | ||||
|     buyToken: string; | ||||
|     orders: Array<Exclude<Order, ['signature', 'exchangeAddress', 'chainId']>>; | ||||
|     signatures: string[]; | ||||
|     maxOrderFillAmounts: BigNumber[]; | ||||
|     sellAmount: BigNumber; | ||||
|     buyAmount: BigNumber; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ABI-encode a `FillQuoteTransformer.TransformData` type. | ||||
|  */ | ||||
| export function encodeFillQuoteTransformerData(data: FillQuoteTransformerData): string { | ||||
|     return fillQuoteTransformerDataEncoder.encode([data]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ABI encoder for `WethTransformer.TransformData` | ||||
|  */ | ||||
| export const wethTransformerDataEncoder = AbiEncoder.create([ | ||||
|     { | ||||
|         name: 'data', | ||||
|         type: 'tuple', | ||||
|         components: [{ name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }], | ||||
|     }, | ||||
| ]); | ||||
|  | ||||
| /** | ||||
|  * `WethTransformer.TransformData` | ||||
|  */ | ||||
| export interface WethTransformerData { | ||||
|     token: string; | ||||
|     amount: BigNumber; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ABI-encode a `WethTransformer.TransformData` type. | ||||
|  */ | ||||
| export function encodeWethTransformerData(data: WethTransformerData): string { | ||||
|     return wethTransformerDataEncoder.encode([data]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ABI encoder for `PayTakerTransformer.TransformData` | ||||
|  */ | ||||
| export const payTakerTransformerDataEncoder = AbiEncoder.create([ | ||||
|     { | ||||
|         name: 'data', | ||||
|         type: 'tuple', | ||||
|         components: [{ name: 'tokens', type: 'address[]' }, { name: 'amounts', type: 'uint256[]' }], | ||||
|     }, | ||||
| ]); | ||||
|  | ||||
| /** | ||||
|  * `PayTakerTransformer.TransformData` | ||||
|  */ | ||||
| export interface PayTakerTransformerData { | ||||
|     tokens: string[]; | ||||
|     amounts: BigNumber[]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ABI-encode a `PayTakerTransformer.TransformData` type. | ||||
|  */ | ||||
| export function encodePayTakerTransformerData(data: PayTakerTransformerData): string { | ||||
|     return payTakerTransformerDataEncoder.encode([data]); | ||||
| } | ||||
| @@ -3,13 +3,16 @@ | ||||
|  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../generated-wrappers/fill_quote_transformer'; | ||||
| export * from '../generated-wrappers/full_migration'; | ||||
| export * from '../generated-wrappers/i_allowance_target'; | ||||
| export * from '../generated-wrappers/i_erc20_transformer'; | ||||
| export * from '../generated-wrappers/i_flash_wallet'; | ||||
| export * from '../generated-wrappers/i_ownable'; | ||||
| export * from '../generated-wrappers/i_simple_function_registry'; | ||||
| export * from '../generated-wrappers/i_token_spender'; | ||||
| export * from '../generated-wrappers/i_transform_erc20'; | ||||
| export * from '../generated-wrappers/initial_migration'; | ||||
| export * from '../generated-wrappers/lib_erc20_transformer'; | ||||
| export * from '../generated-wrappers/pay_taker_transformer'; | ||||
| export * from '../generated-wrappers/puppet'; | ||||
| export * from '../generated-wrappers/weth_transformer'; | ||||
| export * from '../generated-wrappers/zero_ex'; | ||||
|   | ||||
| @@ -7,16 +7,17 @@ import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json'; | ||||
| import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json'; | ||||
| import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json'; | ||||
| import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json'; | ||||
| import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json'; | ||||
| import * as FullMigration from '../test/generated-artifacts/FullMigration.json'; | ||||
| import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json'; | ||||
| import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json'; | ||||
| import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json'; | ||||
| import * as IExchange from '../test/generated-artifacts/IExchange.json'; | ||||
| import * as IFeature from '../test/generated-artifacts/IFeature.json'; | ||||
| import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json'; | ||||
| import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json'; | ||||
| import * as IOwnable from '../test/generated-artifacts/IOwnable.json'; | ||||
| import * as IPuppet from '../test/generated-artifacts/IPuppet.json'; | ||||
| import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json'; | ||||
| import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json'; | ||||
| import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json'; | ||||
| @@ -29,6 +30,7 @@ import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRic | ||||
| import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json'; | ||||
| import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json'; | ||||
| import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json'; | ||||
| import * as LibPuppetRichErrors from '../test/generated-artifacts/LibPuppetRichErrors.json'; | ||||
| import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json'; | ||||
| import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json'; | ||||
| import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json'; | ||||
| @@ -36,37 +38,44 @@ import * as LibStorage from '../test/generated-artifacts/LibStorage.json'; | ||||
| import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json'; | ||||
| import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json'; | ||||
| import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; | ||||
| import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json'; | ||||
| import * as Ownable from '../test/generated-artifacts/Ownable.json'; | ||||
| import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json'; | ||||
| import * as Puppet from '../test/generated-artifacts/Puppet.json'; | ||||
| import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json'; | ||||
| import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json'; | ||||
| import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json'; | ||||
| import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json'; | ||||
| import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json'; | ||||
| import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json'; | ||||
| import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json'; | ||||
| import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json'; | ||||
| import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json'; | ||||
| import * as TestPuppetTarget from '../test/generated-artifacts/TestPuppetTarget.json'; | ||||
| import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json'; | ||||
| import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json'; | ||||
| import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json'; | ||||
| import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json'; | ||||
| import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json'; | ||||
| import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json'; | ||||
| import * as TestWeth from '../test/generated-artifacts/TestWeth.json'; | ||||
| import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json'; | ||||
| import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json'; | ||||
| import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json'; | ||||
| import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json'; | ||||
| import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json'; | ||||
| import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json'; | ||||
| export const artifacts = { | ||||
|     ZeroEx: ZeroEx as ContractArtifact, | ||||
|     LibCommonRichErrors: LibCommonRichErrors as ContractArtifact, | ||||
|     LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact, | ||||
|     LibProxyRichErrors: LibProxyRichErrors as ContractArtifact, | ||||
|     LibPuppetRichErrors: LibPuppetRichErrors as ContractArtifact, | ||||
|     LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact, | ||||
|     LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact, | ||||
|     LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact, | ||||
|     LibWalletRichErrors: LibWalletRichErrors as ContractArtifact, | ||||
|     AllowanceTarget: AllowanceTarget as ContractArtifact, | ||||
|     FlashWallet: FlashWallet as ContractArtifact, | ||||
|     IAllowanceTarget: IAllowanceTarget as ContractArtifact, | ||||
|     IFlashWallet: IFlashWallet as ContractArtifact, | ||||
|     IPuppet: IPuppet as ContractArtifact, | ||||
|     Puppet: Puppet as ContractArtifact, | ||||
|     Bootstrap: Bootstrap as ContractArtifact, | ||||
|     IBootstrap: IBootstrap as ContractArtifact, | ||||
|     IFeature: IFeature as ContractArtifact, | ||||
| @@ -89,19 +98,28 @@ export const artifacts = { | ||||
|     LibStorage: LibStorage as ContractArtifact, | ||||
|     LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact, | ||||
|     LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact, | ||||
|     FillQuoteTransformer: FillQuoteTransformer as ContractArtifact, | ||||
|     IERC20Transformer: IERC20Transformer as ContractArtifact, | ||||
|     LibERC20Transformer: LibERC20Transformer as ContractArtifact, | ||||
|     PayTakerTransformer: PayTakerTransformer as ContractArtifact, | ||||
|     WethTransformer: WethTransformer as ContractArtifact, | ||||
|     IExchange: IExchange as ContractArtifact, | ||||
|     ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact, | ||||
|     TestCallTarget: TestCallTarget as ContractArtifact, | ||||
|     TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact, | ||||
|     TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact, | ||||
|     TestFullMigration: TestFullMigration as ContractArtifact, | ||||
|     TestInitialMigration: TestInitialMigration as ContractArtifact, | ||||
|     TestMigrator: TestMigrator as ContractArtifact, | ||||
|     TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact, | ||||
|     TestMintableERC20Token: TestMintableERC20Token as ContractArtifact, | ||||
|     TestPuppetTarget: TestPuppetTarget as ContractArtifact, | ||||
|     TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact, | ||||
|     TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact, | ||||
|     TestTokenSpender: TestTokenSpender as ContractArtifact, | ||||
|     TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact, | ||||
|     TestTransformERC20: TestTransformERC20 as ContractArtifact, | ||||
|     TestTransformerHost: TestTransformerHost as ContractArtifact, | ||||
|     TestWeth: TestWeth as ContractArtifact, | ||||
|     TestWethTransformerHost: TestWethTransformerHost as ContractArtifact, | ||||
|     TestZeroExFeature: TestZeroExFeature as ContractArtifact, | ||||
| }; | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils'; | ||||
|  | ||||
| import { ETH_TOKEN_ADDRESS } from '../../src/constants'; | ||||
| import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { abis } from '../utils/abis'; | ||||
| @@ -206,8 +207,6 @@ blockchainTests.resets('TransformERC20 feature', env => { | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; | ||||
|  | ||||
|         it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount, with ETH", async () => { | ||||
|             const startingInputTokenBalance = getRandomInteger(0, '100e18'); | ||||
|             await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); | ||||
|   | ||||
| @@ -0,0 +1,849 @@ | ||||
| import { | ||||
|     assertIntegerRoughlyEquals, | ||||
|     blockchainTests, | ||||
|     constants, | ||||
|     expect, | ||||
|     getRandomInteger, | ||||
|     Numberish, | ||||
|     randomAddress, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { assetDataUtils } from '@0x/order-utils'; | ||||
| import { Order } from '@0x/types'; | ||||
| import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { encodeFillQuoteTransformerData, FillQuoteTransformerData } from '../../src/transformer_data_encoders'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { | ||||
|     FillQuoteTransformerContract, | ||||
|     TestFillQuoteTransformerExchangeContract, | ||||
|     TestFillQuoteTransformerHostContract, | ||||
|     TestMintableERC20TokenContract, | ||||
| } from '../wrappers'; | ||||
|  | ||||
| const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants; | ||||
|  | ||||
| blockchainTests.resets('FillQuoteTransformer', env => { | ||||
|     let maker: string; | ||||
|     let feeRecipient: string; | ||||
|     let exchange: TestFillQuoteTransformerExchangeContract; | ||||
|     let transformer: FillQuoteTransformerContract; | ||||
|     let host: TestFillQuoteTransformerHostContract; | ||||
|     let makerToken: TestMintableERC20TokenContract; | ||||
|     let takerToken: TestMintableERC20TokenContract; | ||||
|     let takerFeeToken: TestMintableERC20TokenContract; | ||||
|     let singleProtocolFee: BigNumber; | ||||
|  | ||||
|     const GAS_PRICE = 1337; | ||||
|  | ||||
|     before(async () => { | ||||
|         [maker, feeRecipient] = await env.getAccountAddressesAsync(); | ||||
|         exchange = await TestFillQuoteTransformerExchangeContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestFillQuoteTransformerExchange, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         transformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.FillQuoteTransformer, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             exchange.address, | ||||
|         ); | ||||
|         host = await TestFillQuoteTransformerHostContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestFillQuoteTransformerHost, | ||||
|             env.provider, | ||||
|             { | ||||
|                 ...env.txDefaults, | ||||
|                 gasPrice: GAS_PRICE, | ||||
|             }, | ||||
|             artifacts, | ||||
|         ); | ||||
|         [makerToken, takerToken, takerFeeToken] = await Promise.all( | ||||
|             _.times(3, async () => | ||||
|                 TestMintableERC20TokenContract.deployFrom0xArtifactAsync( | ||||
|                     artifacts.TestMintableERC20Token, | ||||
|                     env.provider, | ||||
|                     env.txDefaults, | ||||
|                     artifacts, | ||||
|                 ), | ||||
|             ), | ||||
|         ); | ||||
|         singleProtocolFee = (await exchange.protocolFeeMultiplier().callAsync()).times(GAS_PRICE); | ||||
|     }); | ||||
|  | ||||
|     type FilledOrder = Order & { filledTakerAssetAmount: BigNumber }; | ||||
|  | ||||
|     function createOrder(fields: Partial<Order> = {}): FilledOrder { | ||||
|         return { | ||||
|             chainId: 1, | ||||
|             exchangeAddress: exchange.address, | ||||
|             expirationTimeSeconds: ZERO_AMOUNT, | ||||
|             salt: ZERO_AMOUNT, | ||||
|             senderAddress: NULL_ADDRESS, | ||||
|             takerAddress: NULL_ADDRESS, | ||||
|             makerAddress: maker, | ||||
|             feeRecipientAddress: feeRecipient, | ||||
|             makerAssetAmount: getRandomInteger('0.1e18', '1e18'), | ||||
|             takerAssetAmount: getRandomInteger('0.1e18', '1e18'), | ||||
|             makerFee: ZERO_AMOUNT, | ||||
|             takerFee: getRandomInteger('0.001e18', '0.1e18'), | ||||
|             makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address), | ||||
|             takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address), | ||||
|             makerFeeAssetData: NULL_BYTES, | ||||
|             takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address), | ||||
|             filledTakerAssetAmount: ZERO_AMOUNT, | ||||
|             ...fields, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     interface QuoteFillResults { | ||||
|         makerAssetBought: BigNumber; | ||||
|         takerAssetSpent: BigNumber; | ||||
|         protocolFeePaid: BigNumber; | ||||
|     } | ||||
|  | ||||
|     const ZERO_QUOTE_FILL_RESULTS = { | ||||
|         makerAssetBought: ZERO_AMOUNT, | ||||
|         takerAssetSpent: ZERO_AMOUNT, | ||||
|         protocolFeePaid: ZERO_AMOUNT, | ||||
|     }; | ||||
|  | ||||
|     function getExpectedSellQuoteFillResults( | ||||
|         orders: FilledOrder[], | ||||
|         takerAssetFillAmount: BigNumber = constants.MAX_UINT256, | ||||
|     ): QuoteFillResults { | ||||
|         const qfr = { ...ZERO_QUOTE_FILL_RESULTS }; | ||||
|         for (const order of orders) { | ||||
|             if (qfr.takerAssetSpent.gte(takerAssetFillAmount)) { | ||||
|                 break; | ||||
|             } | ||||
|             const singleFillAmount = BigNumber.min( | ||||
|                 takerAssetFillAmount.minus(qfr.takerAssetSpent), | ||||
|                 order.takerAssetAmount.minus(order.filledTakerAssetAmount), | ||||
|             ); | ||||
|             const fillRatio = singleFillAmount.div(order.takerAssetAmount); | ||||
|             qfr.takerAssetSpent = qfr.takerAssetSpent.plus(singleFillAmount); | ||||
|             qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee); | ||||
|             qfr.makerAssetBought = qfr.makerAssetBought.plus( | ||||
|                 fillRatio.times(order.makerAssetAmount).integerValue(BigNumber.ROUND_DOWN), | ||||
|             ); | ||||
|             const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_DOWN); | ||||
|             if (order.takerAssetData === order.takerFeeAssetData) { | ||||
|                 // Taker fee is in taker asset. | ||||
|                 qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee); | ||||
|             } else if (order.makerAssetData === order.takerFeeAssetData) { | ||||
|                 // Taker fee is in maker asset. | ||||
|                 qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee); | ||||
|             } | ||||
|         } | ||||
|         return qfr; | ||||
|     } | ||||
|  | ||||
|     function getExpectedBuyQuoteFillResults( | ||||
|         orders: FilledOrder[], | ||||
|         makerAssetFillAmount: BigNumber = constants.MAX_UINT256, | ||||
|     ): QuoteFillResults { | ||||
|         const qfr = { ...ZERO_QUOTE_FILL_RESULTS }; | ||||
|         for (const order of orders) { | ||||
|             if (qfr.makerAssetBought.gte(makerAssetFillAmount)) { | ||||
|                 break; | ||||
|             } | ||||
|             const filledMakerAssetAmount = order.filledTakerAssetAmount | ||||
|                 .times(order.makerAssetAmount.div(order.takerAssetAmount)) | ||||
|                 .integerValue(BigNumber.ROUND_DOWN); | ||||
|             const singleFillAmount = BigNumber.min( | ||||
|                 makerAssetFillAmount.minus(qfr.makerAssetBought), | ||||
|                 order.makerAssetAmount.minus(filledMakerAssetAmount), | ||||
|             ); | ||||
|             const fillRatio = singleFillAmount.div(order.makerAssetAmount); | ||||
|             qfr.takerAssetSpent = qfr.takerAssetSpent.plus( | ||||
|                 fillRatio.times(order.takerAssetAmount).integerValue(BigNumber.ROUND_UP), | ||||
|             ); | ||||
|             qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee); | ||||
|             qfr.makerAssetBought = qfr.makerAssetBought.plus(singleFillAmount); | ||||
|             const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_UP); | ||||
|             if (order.takerAssetData === order.takerFeeAssetData) { | ||||
|                 // Taker fee is in taker asset. | ||||
|                 qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee); | ||||
|             } else if (order.makerAssetData === order.takerFeeAssetData) { | ||||
|                 // Taker fee is in maker asset. | ||||
|                 qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee); | ||||
|             } | ||||
|         } | ||||
|         return qfr; | ||||
|     } | ||||
|  | ||||
|     interface Balances { | ||||
|         makerAssetBalance: BigNumber; | ||||
|         takerAssetBalance: BigNumber; | ||||
|         takerFeeBalance: BigNumber; | ||||
|         protocolFeeBalance: BigNumber; | ||||
|     } | ||||
|  | ||||
|     const ZERO_BALANCES = { | ||||
|         makerAssetBalance: ZERO_AMOUNT, | ||||
|         takerAssetBalance: ZERO_AMOUNT, | ||||
|         takerFeeBalance: ZERO_AMOUNT, | ||||
|         protocolFeeBalance: ZERO_AMOUNT, | ||||
|     }; | ||||
|  | ||||
|     async function getBalancesAsync(owner: string): Promise<Balances> { | ||||
|         const balances = { ...ZERO_BALANCES }; | ||||
|         [ | ||||
|             balances.makerAssetBalance, | ||||
|             balances.takerAssetBalance, | ||||
|             balances.takerFeeBalance, | ||||
|             balances.protocolFeeBalance, | ||||
|         ] = await Promise.all([ | ||||
|             makerToken.balanceOf(owner).callAsync(), | ||||
|             takerToken.balanceOf(owner).callAsync(), | ||||
|             takerFeeToken.balanceOf(owner).callAsync(), | ||||
|             env.web3Wrapper.getBalanceInWeiAsync(owner), | ||||
|         ]); | ||||
|         return balances; | ||||
|     } | ||||
|  | ||||
|     function assertBalances(actual: Balances, expected: Balances): void { | ||||
|         assertIntegerRoughlyEquals(actual.makerAssetBalance, expected.makerAssetBalance, 10, 'makerAssetBalance'); | ||||
|         assertIntegerRoughlyEquals(actual.takerAssetBalance, expected.takerAssetBalance, 10, 'takerAssetBalance'); | ||||
|         assertIntegerRoughlyEquals(actual.takerFeeBalance, expected.takerFeeBalance, 10, 'takerFeeBalance'); | ||||
|         assertIntegerRoughlyEquals(actual.protocolFeeBalance, expected.protocolFeeBalance, 10, 'protocolFeeBalance'); | ||||
|     } | ||||
|  | ||||
|     function encodeTransformData(fields: Partial<FillQuoteTransformerData> = {}): string { | ||||
|         return encodeFillQuoteTransformerData({ | ||||
|             sellToken: takerToken.address, | ||||
|             buyToken: makerToken.address, | ||||
|             orders: [], | ||||
|             signatures: [], | ||||
|             maxOrderFillAmounts: [], | ||||
|             sellAmount: MAX_UINT256, | ||||
|             buyAmount: ZERO_AMOUNT, | ||||
|             ...fields, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function encodeExchangeBehavior( | ||||
|         filledTakerAssetAmount: Numberish = 0, | ||||
|         makerAssetMintRatio: Numberish = 1.0, | ||||
|     ): string { | ||||
|         return hexUtils.slice( | ||||
|             exchange | ||||
|                 .encodeBehaviorData({ | ||||
|                     filledTakerAssetAmount: new BigNumber(filledTakerAssetAmount), | ||||
|                     makerAssetMintRatio: new BigNumber(makerAssetMintRatio).times('1e18').integerValue(), | ||||
|                 }) | ||||
|                 .getABIEncodedTransactionData(), | ||||
|             4, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     const ERC20_ASSET_PROXY_ID = '0xf47261b0'; | ||||
|  | ||||
|     describe('sell quotes', () => { | ||||
|         it('can fully sell to a single order quote', async () => { | ||||
|             const orders = _.times(1, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can fully sell to multi order quote', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can partially sell to single order quote', async () => { | ||||
|             const orders = _.times(1, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults( | ||||
|                 orders, | ||||
|                 getExpectedSellQuoteFillResults(orders).takerAssetSpent.dividedToIntegerBy(2), | ||||
|             ); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can partially sell to multi order quote and refund unused protocol fees', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders.slice(0, 2)); | ||||
|             const maxProtocolFees = singleProtocolFee.times(orders.length); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: maxProtocolFees }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|                 protocolFeeBalance: singleProtocolFee, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can sell to multi order quote with a failing order', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             // First order will fail. | ||||
|             const validOrders = orders.slice(1); | ||||
|             const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())]; | ||||
|             const qfr = getExpectedSellQuoteFillResults(validOrders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('succeeds if an order transfers too few maker tokens', async () => { | ||||
|             const mintScale = 0.5; | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             // First order mints less than expected. | ||||
|             const signatures = [ | ||||
|                 encodeExchangeBehavior(0, mintScale), | ||||
|                 ...orders.slice(1).map(() => encodeExchangeBehavior()), | ||||
|             ]; | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought | ||||
|                     .minus(orders[0].makerAssetAmount.times(1 - mintScale)) | ||||
|                     .integerValue(BigNumber.ROUND_DOWN), | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can fail if an order is partially filled', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             // First order is partially filled. | ||||
|             const filledOrder = { | ||||
|                 ...orders[0], | ||||
|                 filledTakerAssetAmount: orders[0].takerAssetAmount.dividedToIntegerBy(2), | ||||
|             }; | ||||
|             // First order is partially filled. | ||||
|             const signatures = [ | ||||
|                 encodeExchangeBehavior(filledOrder.filledTakerAssetAmount), | ||||
|                 ...orders.slice(1).map(() => encodeExchangeBehavior()), | ||||
|             ]; | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError( | ||||
|                     takerToken.address, | ||||
|                     getExpectedSellQuoteFillResults([filledOrder, ...orders.slice(1)]).takerAssetSpent, | ||||
|                     qfr.takerAssetSpent, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('fails if not enough protocol fee provided', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.InsufficientProtocolFeeError( | ||||
|                     singleProtocolFee.minus(1), | ||||
|                     singleProtocolFee, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('can sell less than the taker token balance', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const takerTokenBalance = qfr.takerAssetSpent.times(1.01).integerValue(); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     takerTokenBalance, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         sellAmount: qfr.takerAssetSpent, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|                 takerAssetBalance: qfr.takerAssetSpent.times(0.01).integerValue(), | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('fails to sell more than the taker token balance', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const takerTokenBalance = qfr.takerAssetSpent.times(0.99).integerValue(); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     takerTokenBalance, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         sellAmount: qfr.takerAssetSpent, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError( | ||||
|                     takerToken.address, | ||||
|                     getExpectedSellQuoteFillResults(orders.slice(0, 2)).takerAssetSpent, | ||||
|                     qfr.takerAssetSpent, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('can fully sell to a single order with maker asset taker fees', async () => { | ||||
|             const orders = _.times(1, () => | ||||
|                 createOrder({ | ||||
|                     takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address), | ||||
|                 }), | ||||
|             ); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('fails if an order has a non-standard taker fee asset', async () => { | ||||
|             const BAD_ASSET_DATA = hexUtils.random(36); | ||||
|             const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA })); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('fails if an order has a fee asset that is neither maker or taker asset', async () => { | ||||
|             const badToken = randomAddress(); | ||||
|             const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken)); | ||||
|             const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA })); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken)); | ||||
|         }); | ||||
|  | ||||
|         it('respects `maxOrderFillAmounts`', async () => { | ||||
|             const orders = _.times(2, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders.slice(1)); | ||||
|             const protocolFee = singleProtocolFee.times(2); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         // Skip the first order. | ||||
|                         maxOrderFillAmounts: [ZERO_AMOUNT], | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: protocolFee }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('buy quotes', () => { | ||||
|         it('can fully buy from a single order quote', async () => { | ||||
|             const orders = _.times(1, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can fully buy from a multi order quote', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can partially buy from a single order quote', async () => { | ||||
|             const orders = _.times(1, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults( | ||||
|                 orders, | ||||
|                 getExpectedBuyQuoteFillResults(orders).makerAssetBought.dividedToIntegerBy(2), | ||||
|             ); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can partially buy from multi order quote and refund unused protocol fees', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders.slice(0, 2)); | ||||
|             const maxProtocolFees = singleProtocolFee.times(orders.length); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: maxProtocolFees }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|                 protocolFeeBalance: singleProtocolFee, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can buy from multi order quote with a failing order', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             // First order will fail. | ||||
|             const validOrders = orders.slice(1); | ||||
|             const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())]; | ||||
|             const qfr = getExpectedBuyQuoteFillResults(validOrders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('succeeds if an order transfers too many maker tokens', async () => { | ||||
|             const orders = _.times(2, () => createOrder()); | ||||
|             // First order will mint its tokens + the maker tokens of the second. | ||||
|             const mintScale = orders[1].makerAssetAmount.div(orders[0].makerAssetAmount.minus(1)).plus(1); | ||||
|             const signatures = [ | ||||
|                 encodeExchangeBehavior(0, mintScale), | ||||
|                 ...orders.slice(1).map(() => encodeExchangeBehavior()), | ||||
|             ]; | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: orders[0].makerAssetAmount.times(mintScale).integerValue(BigNumber.ROUND_DOWN), | ||||
|                 takerAssetBalance: orders[1].takerAssetAmount.plus(orders[1].takerFee), | ||||
|                 protocolFeeBalance: singleProtocolFee, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('fails to buy more than available in orders', async () => { | ||||
|             const orders = _.times(3, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought.plus(1), | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.IncompleteFillBuyQuoteError( | ||||
|                     makerToken.address, | ||||
|                     qfr.makerAssetBought, | ||||
|                     qfr.makerAssetBought.plus(1), | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('can fully buy from a single order with maker asset taker fees', async () => { | ||||
|             const orders = _.times(1, () => | ||||
|                 createOrder({ | ||||
|                     takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address), | ||||
|                 }), | ||||
|             ); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedBuyQuoteFillResults(orders); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('fails if an order has a non-standard taker fee asset', async () => { | ||||
|             const BAD_ASSET_DATA = hexUtils.random(36); | ||||
|             const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA })); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA), | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('fails if an order has a fee asset that is neither maker or taker asset', async () => { | ||||
|             const badToken = randomAddress(); | ||||
|             const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken)); | ||||
|             const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA })); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders); | ||||
|             const tx = host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid }); | ||||
|             return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken)); | ||||
|         }); | ||||
|  | ||||
|         it('respects `maxOrderFillAmounts`', async () => { | ||||
|             const orders = _.times(2, () => createOrder()); | ||||
|             const signatures = orders.map(() => encodeExchangeBehavior()); | ||||
|             const qfr = getExpectedSellQuoteFillResults(orders.slice(1)); | ||||
|             const protocolFee = singleProtocolFee.times(2); | ||||
|             await host | ||||
|                 .executeTransform( | ||||
|                     transformer.address, | ||||
|                     takerToken.address, | ||||
|                     qfr.takerAssetSpent, | ||||
|                     encodeTransformData({ | ||||
|                         orders, | ||||
|                         signatures, | ||||
|                         buyAmount: qfr.makerAssetBought, | ||||
|                         // Skip the first order. | ||||
|                         maxOrderFillAmounts: [ZERO_AMOUNT], | ||||
|                     }), | ||||
|                 ) | ||||
|                 .awaitTransactionSuccessAsync({ value: protocolFee }); | ||||
|             assertBalances(await getBalancesAsync(host.address), { | ||||
|                 ...ZERO_BALANCES, | ||||
|                 makerAssetBalance: qfr.makerAssetBought, | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										147
									
								
								contracts/zero-ex/test/transformers/pay_taker_transformer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								contracts/zero-ex/test/transformers/pay_taker_transformer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; | ||||
| import { BigNumber, hexUtils } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { ETH_TOKEN_ADDRESS } from '../../src/constants'; | ||||
| import { encodePayTakerTransformerData } from '../../src/transformer_data_encoders'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { PayTakerTransformerContract, TestMintableERC20TokenContract, TestTransformerHostContract } from '../wrappers'; | ||||
|  | ||||
| const { MAX_UINT256, ZERO_AMOUNT } = constants; | ||||
|  | ||||
| blockchainTests.resets('PayTakerTransformer', env => { | ||||
|     let caller: string; | ||||
|     const taker = randomAddress(); | ||||
|     let token: TestMintableERC20TokenContract; | ||||
|     let transformer: PayTakerTransformerContract; | ||||
|     let host: TestTransformerHostContract; | ||||
|  | ||||
|     before(async () => { | ||||
|         [caller] = await env.getAccountAddressesAsync(); | ||||
|         token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestMintableERC20Token, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         transformer = await PayTakerTransformerContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.PayTakerTransformer, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         host = await TestTransformerHostContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestTransformerHost, | ||||
|             env.provider, | ||||
|             { ...env.txDefaults, from: caller }, | ||||
|             artifacts, | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     interface Balances { | ||||
|         ethBalance: BigNumber; | ||||
|         tokenBalance: BigNumber; | ||||
|     } | ||||
|  | ||||
|     const ZERO_BALANCES = { | ||||
|         ethBalance: ZERO_AMOUNT, | ||||
|         tokenBalance: ZERO_AMOUNT, | ||||
|     }; | ||||
|  | ||||
|     async function getBalancesAsync(owner: string): Promise<Balances> { | ||||
|         return { | ||||
|             ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner), | ||||
|             tokenBalance: await token.balanceOf(owner).callAsync(), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     async function mintHostTokensAsync(amount: BigNumber): Promise<void> { | ||||
|         await token.mint(host.address, amount).awaitTransactionSuccessAsync(); | ||||
|     } | ||||
|  | ||||
|     async function sendEtherAsync(to: string, amount: BigNumber): Promise<void> { | ||||
|         await env.web3Wrapper.awaitTransactionSuccessAsync( | ||||
|             await env.web3Wrapper.sendTransactionAsync({ | ||||
|                 ...env.txDefaults, | ||||
|                 to, | ||||
|                 from: caller, | ||||
|                 value: amount, | ||||
|             }), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     it('can transfer a token and ETH', async () => { | ||||
|         const amounts = _.times(2, () => getRandomInteger(1, '1e18')); | ||||
|         const data = encodePayTakerTransformerData({ | ||||
|             amounts, | ||||
|             tokens: [token.address, ETH_TOKEN_ADDRESS], | ||||
|         }); | ||||
|         await mintHostTokensAsync(amounts[0]); | ||||
|         await sendEtherAsync(host.address, amounts[1]); | ||||
|         await host | ||||
|             .rawExecuteTransform(transformer.address, hexUtils.random(), taker, data) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES); | ||||
|         expect(await getBalancesAsync(taker)).to.deep.eq({ | ||||
|             tokenBalance: amounts[0], | ||||
|             ethBalance: amounts[1], | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can transfer all of a token and ETH', async () => { | ||||
|         const amounts = _.times(2, () => getRandomInteger(1, '1e18')); | ||||
|         const data = encodePayTakerTransformerData({ | ||||
|             amounts: [MAX_UINT256, MAX_UINT256], | ||||
|             tokens: [token.address, ETH_TOKEN_ADDRESS], | ||||
|         }); | ||||
|         await mintHostTokensAsync(amounts[0]); | ||||
|         await sendEtherAsync(host.address, amounts[1]); | ||||
|         await host | ||||
|             .rawExecuteTransform(transformer.address, hexUtils.random(), taker, data) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES); | ||||
|         expect(await getBalancesAsync(taker)).to.deep.eq({ | ||||
|             tokenBalance: amounts[0], | ||||
|             ethBalance: amounts[1], | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can transfer all of a token and ETH (empty amounts)', async () => { | ||||
|         const amounts = _.times(2, () => getRandomInteger(1, '1e18')); | ||||
|         const data = encodePayTakerTransformerData({ | ||||
|             amounts: [], | ||||
|             tokens: [token.address, ETH_TOKEN_ADDRESS], | ||||
|         }); | ||||
|         await mintHostTokensAsync(amounts[0]); | ||||
|         await sendEtherAsync(host.address, amounts[1]); | ||||
|         await host | ||||
|             .rawExecuteTransform(transformer.address, hexUtils.random(), taker, data) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES); | ||||
|         expect(await getBalancesAsync(taker)).to.deep.eq({ | ||||
|             tokenBalance: amounts[0], | ||||
|             ethBalance: amounts[1], | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can transfer less than the balance of a token and ETH', async () => { | ||||
|         const amounts = _.times(2, () => getRandomInteger(1, '1e18')); | ||||
|         const data = encodePayTakerTransformerData({ | ||||
|             amounts: amounts.map(a => a.dividedToIntegerBy(2)), | ||||
|             tokens: [token.address, ETH_TOKEN_ADDRESS], | ||||
|         }); | ||||
|         await mintHostTokensAsync(amounts[0]); | ||||
|         await sendEtherAsync(host.address, amounts[1]); | ||||
|         await host | ||||
|             .rawExecuteTransform(transformer.address, hexUtils.random(), taker, data) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         expect(await getBalancesAsync(host.address)).to.deep.eq({ | ||||
|             tokenBalance: amounts[0].minus(amounts[0].dividedToIntegerBy(2)), | ||||
|             ethBalance: amounts[1].minus(amounts[1].dividedToIntegerBy(2)), | ||||
|         }); | ||||
|         expect(await getBalancesAsync(taker)).to.deep.eq({ | ||||
|             tokenBalance: amounts[0].dividedToIntegerBy(2), | ||||
|             ethBalance: amounts[1].dividedToIntegerBy(2), | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										147
									
								
								contracts/zero-ex/test/transformers/weth_transformer_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								contracts/zero-ex/test/transformers/weth_transformer_test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; | ||||
| import { BigNumber, ZeroExRevertErrors } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { ETH_TOKEN_ADDRESS } from '../../src/constants'; | ||||
| import { encodeWethTransformerData } from '../../src/transformer_data_encoders'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { TestWethContract, TestWethTransformerHostContract, WethTransformerContract } from '../wrappers'; | ||||
|  | ||||
| const { MAX_UINT256, ZERO_AMOUNT } = constants; | ||||
|  | ||||
| blockchainTests.resets('WethTransformer', env => { | ||||
|     let weth: TestWethContract; | ||||
|     let transformer: WethTransformerContract; | ||||
|     let host: TestWethTransformerHostContract; | ||||
|  | ||||
|     before(async () => { | ||||
|         weth = await TestWethContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestWeth, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         transformer = await WethTransformerContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.WethTransformer, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             weth.address, | ||||
|         ); | ||||
|         host = await TestWethTransformerHostContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.TestWethTransformerHost, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             weth.address, | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     interface Balances { | ||||
|         ethBalance: BigNumber; | ||||
|         wethBalance: BigNumber; | ||||
|     } | ||||
|  | ||||
|     async function getHostBalancesAsync(): Promise<Balances> { | ||||
|         return { | ||||
|             ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(host.address), | ||||
|             wethBalance: await weth.balanceOf(host.address).callAsync(), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     it('fails if the token is neither ETH or WETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount, | ||||
|             token: randomAddress(), | ||||
|         }); | ||||
|         const tx = host | ||||
|             .executeTransform(amount, transformer.address, data) | ||||
|             .awaitTransactionSuccessAsync({ value: amount }); | ||||
|         return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTransformDataError(data)); | ||||
|     }); | ||||
|  | ||||
|     it('can unwrap WETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount, | ||||
|             token: weth.address, | ||||
|         }); | ||||
|         await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: amount, | ||||
|             wethBalance: ZERO_AMOUNT, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can unwrap all WETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount: MAX_UINT256, | ||||
|             token: weth.address, | ||||
|         }); | ||||
|         await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: amount, | ||||
|             wethBalance: ZERO_AMOUNT, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can unwrap some WETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount: amount.dividedToIntegerBy(2), | ||||
|             token: weth.address, | ||||
|         }); | ||||
|         await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: amount.dividedToIntegerBy(2), | ||||
|             wethBalance: amount.minus(amount.dividedToIntegerBy(2)), | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can wrap ETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount, | ||||
|             token: ETH_TOKEN_ADDRESS, | ||||
|         }); | ||||
|         await host | ||||
|             .executeTransform(ZERO_AMOUNT, transformer.address, data) | ||||
|             .awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: ZERO_AMOUNT, | ||||
|             wethBalance: amount, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can wrap all ETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount: MAX_UINT256, | ||||
|             token: ETH_TOKEN_ADDRESS, | ||||
|         }); | ||||
|         await host | ||||
|             .executeTransform(ZERO_AMOUNT, transformer.address, data) | ||||
|             .awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: ZERO_AMOUNT, | ||||
|             wethBalance: amount, | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('can wrap some ETH', async () => { | ||||
|         const amount = getRandomInteger(1, '1e18'); | ||||
|         const data = encodeWethTransformerData({ | ||||
|             amount: amount.dividedToIntegerBy(2), | ||||
|             token: ETH_TOKEN_ADDRESS, | ||||
|         }); | ||||
|         await host | ||||
|             .executeTransform(ZERO_AMOUNT, transformer.address, data) | ||||
|             .awaitTransactionSuccessAsync({ value: amount }); | ||||
|         expect(await getHostBalancesAsync()).to.deep.eq({ | ||||
|             ethBalance: amount.minus(amount.dividedToIntegerBy(2)), | ||||
|             wethBalance: amount.dividedToIntegerBy(2), | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -5,12 +5,14 @@ | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/allowance_target'; | ||||
| export * from '../test/generated-wrappers/bootstrap'; | ||||
| export * from '../test/generated-wrappers/fill_quote_transformer'; | ||||
| export * from '../test/generated-wrappers/fixin_common'; | ||||
| export * from '../test/generated-wrappers/flash_wallet'; | ||||
| export * from '../test/generated-wrappers/full_migration'; | ||||
| export * from '../test/generated-wrappers/i_allowance_target'; | ||||
| export * from '../test/generated-wrappers/i_bootstrap'; | ||||
| export * from '../test/generated-wrappers/i_erc20_transformer'; | ||||
| export * from '../test/generated-wrappers/i_exchange'; | ||||
| export * from '../test/generated-wrappers/i_feature'; | ||||
| export * from '../test/generated-wrappers/i_flash_wallet'; | ||||
| export * from '../test/generated-wrappers/i_ownable'; | ||||
| @@ -49,8 +51,12 @@ export * from '../test/generated-wrappers/test_simple_function_registry_feature_ | ||||
| export * from '../test/generated-wrappers/test_token_spender'; | ||||
| export * from '../test/generated-wrappers/test_token_spender_erc20_token'; | ||||
| export * from '../test/generated-wrappers/test_transform_erc20'; | ||||
| export * from '../test/generated-wrappers/test_transformer_host'; | ||||
| export * from '../test/generated-wrappers/test_weth'; | ||||
| export * from '../test/generated-wrappers/test_weth_transformer_host'; | ||||
| export * from '../test/generated-wrappers/test_zero_ex_feature'; | ||||
| export * from '../test/generated-wrappers/token_spender'; | ||||
| export * from '../test/generated-wrappers/token_spender_puppet'; | ||||
| export * from '../test/generated-wrappers/transform_erc20'; | ||||
| export * from '../test/generated-wrappers/weth_transformer'; | ||||
| export * from '../test/generated-wrappers/zero_ex'; | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|     "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, | ||||
|     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], | ||||
|     "files": [ | ||||
|         "generated-artifacts/FillQuoteTransformer.json", | ||||
|         "generated-artifacts/FullMigration.json", | ||||
|         "generated-artifacts/IAllowanceTarget.json", | ||||
|         "generated-artifacts/IERC20Transformer.json", | ||||
| @@ -15,12 +16,14 @@ | ||||
|         "generated-artifacts/ZeroEx.json", | ||||
|         "test/generated-artifacts/AllowanceTarget.json", | ||||
|         "test/generated-artifacts/Bootstrap.json", | ||||
|         "test/generated-artifacts/FillQuoteTransformer.json", | ||||
|         "test/generated-artifacts/FixinCommon.json", | ||||
|         "test/generated-artifacts/FlashWallet.json", | ||||
|         "test/generated-artifacts/FullMigration.json", | ||||
|         "test/generated-artifacts/IAllowanceTarget.json", | ||||
|         "test/generated-artifacts/IBootstrap.json", | ||||
|         "test/generated-artifacts/IERC20Transformer.json", | ||||
|         "test/generated-artifacts/IExchange.json", | ||||
|         "test/generated-artifacts/IFeature.json", | ||||
|         "test/generated-artifacts/IFlashWallet.json", | ||||
|         "test/generated-artifacts/IOwnable.json", | ||||
| @@ -59,10 +62,14 @@ | ||||
|         "test/generated-artifacts/TestTokenSpender.json", | ||||
|         "test/generated-artifacts/TestTokenSpenderERC20Token.json", | ||||
|         "test/generated-artifacts/TestTransformERC20.json", | ||||
|         "test/generated-artifacts/TestTransformerHost.json", | ||||
|         "test/generated-artifacts/TestWeth.json", | ||||
|         "test/generated-artifacts/TestWethTransformerHost.json", | ||||
|         "test/generated-artifacts/TestZeroExFeature.json", | ||||
|         "test/generated-artifacts/TokenSpender.json", | ||||
|         "test/generated-artifacts/TokenSpenderPuppet.json", | ||||
|         "test/generated-artifacts/TransformERC20.json", | ||||
|         "test/generated-artifacts/WethTransformer.json", | ||||
|         "test/generated-artifacts/ZeroEx.json" | ||||
|     ], | ||||
|     "exclude": ["./deploy/solc/solc_bin"] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user