MultiplexFeature and BatchFillNativeOrdersFeature (#140)
* WrappedFillFeature
* Address internal feedback
* create features/interfaces/ directory
* Split NativeOrdersFeature into mixins
* Rename mixins to use NativeOrders namespace
* Add BatchFillNativeOrdersFeature
* Rename WrapperFillFeature => MultiplexFeature and add natspec comments
* Emit LiquidityProviderSwap event
* post-rebase fixes
* Multiplex mainnet fork tests
* lint
* Add tests for batch fill functions
* Remove market functions
* Addres PR feedback
* Remove nested _batchFill calls from _multiHopFill
* Add BatchFillIncompleteRevertError type
* Use call{value: amount}() instead of transfer(amount)
* Remove outdated comment
* Update some comments
* Add events
* Address spot-check recommendations
* Remove-top level events, add ExpiredRfqOrder event
* Update changelog
* Change ExpiredRfqOrder event
* Update IZeroEx artifact and contract wrapper
			
			
This commit is contained in:
		| @@ -6,6 +6,7 @@ export { | ||||
|     WETH9Events, | ||||
|     WETH9DepositEventArgs, | ||||
|     WETH9TransferEventArgs, | ||||
|     WETH9WithdrawalEventArgs, | ||||
|     ZRXTokenContract, | ||||
|     DummyERC20TokenTransferEventArgs, | ||||
|     ERC20TokenEventArgs, | ||||
|   | ||||
| @@ -9,6 +9,10 @@ | ||||
|             { | ||||
|                 "note": "Emit `LiquidityProviderFill` event in `CurveLiquidityProvider`", | ||||
|                 "pr": 143 | ||||
|             }, | ||||
|             { | ||||
|                 "note": "Add BatchFillNativeOrdersFeature and MultiplexFeature", | ||||
|                 "pr": 140 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|   | ||||
| @@ -20,14 +20,16 @@ | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./features/IOwnableFeature.sol"; | ||||
| import "./features/ISimpleFunctionRegistryFeature.sol"; | ||||
| import "./features/ITokenSpenderFeature.sol"; | ||||
| import "./features/ITransformERC20Feature.sol"; | ||||
| import "./features/IMetaTransactionsFeature.sol"; | ||||
| import "./features/IUniswapFeature.sol"; | ||||
| import "./features/ILiquidityProviderFeature.sol"; | ||||
| import "./features/INativeOrdersFeature.sol"; | ||||
| import "./features/interfaces/IOwnableFeature.sol"; | ||||
| import "./features/interfaces/ISimpleFunctionRegistryFeature.sol"; | ||||
| import "./features/interfaces/ITokenSpenderFeature.sol"; | ||||
| import "./features/interfaces/ITransformERC20Feature.sol"; | ||||
| import "./features/interfaces/IMetaTransactionsFeature.sol"; | ||||
| import "./features/interfaces/IUniswapFeature.sol"; | ||||
| import "./features/interfaces/ILiquidityProviderFeature.sol"; | ||||
| import "./features/interfaces/INativeOrdersFeature.sol"; | ||||
| import "./features/interfaces/IBatchFillNativeOrdersFeature.sol"; | ||||
| import "./features/interfaces/IMultiplexFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Interface for a fully featured Exchange Proxy. | ||||
| @@ -39,7 +41,9 @@ interface IZeroEx is | ||||
|     IMetaTransactionsFeature, | ||||
|     IUniswapFeature, | ||||
|     ILiquidityProviderFeature, | ||||
|     INativeOrdersFeature | ||||
|     INativeOrdersFeature, | ||||
|     IBatchFillNativeOrdersFeature, | ||||
|     IMultiplexFeature | ||||
| { | ||||
|     // solhint-disable state-visibility | ||||
|  | ||||
|   | ||||
| @@ -170,4 +170,21 @@ library LibNativeOrdersRichErrors { | ||||
|             maker | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function BatchFillIncompleteError( | ||||
|         bytes32 orderHash, | ||||
|         uint256 takerTokenFilledAmount, | ||||
|         uint256 takerTokenFillAmount | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (bytes memory) | ||||
|     { | ||||
|         return abi.encodeWithSelector( | ||||
|             bytes4(keccak256("BatchFillIncompleteError(bytes32,uint256,uint256)")), | ||||
|             orderHash, | ||||
|             takerTokenFilledAmount, | ||||
|             takerTokenFillAmount | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,198 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 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-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "../errors/LibNativeOrdersRichErrors.sol"; | ||||
| import "../fixins/FixinCommon.sol"; | ||||
| import "../fixins/FixinEIP712.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/IBatchFillNativeOrdersFeature.sol"; | ||||
| import "./interfaces/INativeOrdersFeature.sol"; | ||||
| import "./libs/LibNativeOrder.sol"; | ||||
| import "./libs/LibSignature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Feature for batch/market filling limit and RFQ orders. | ||||
| contract BatchFillNativeOrdersFeature is | ||||
|     IFeature, | ||||
|     IBatchFillNativeOrdersFeature, | ||||
|     FixinCommon, | ||||
|     FixinEIP712 | ||||
| { | ||||
|     using LibSafeMathV06 for uint128; | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     /// @dev Name of this feature. | ||||
|     string public constant override FEATURE_NAME = "BatchFill"; | ||||
|     /// @dev Version of this feature. | ||||
|     uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); | ||||
|  | ||||
|     constructor(address zeroExAddress) | ||||
|         public | ||||
|         FixinEIP712(zeroExAddress) | ||||
|     { | ||||
|         // solhint-disable no-empty-blocks | ||||
|     } | ||||
|  | ||||
|     /// @dev Initialize and register this feature. | ||||
|     ///      Should be delegatecalled by `Migrate.migrate()`. | ||||
|     /// @return success `LibMigrate.SUCCESS` on success. | ||||
|     function migrate() | ||||
|         external | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         _registerFeatureFunction(this.batchFillLimitOrders.selector); | ||||
|         _registerFeatureFunction(this.batchFillRfqOrders.selector); | ||||
|         return LibMigrate.MIGRATE_SUCCESS; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills multiple limit orders. | ||||
|     /// @param orders Array of limit orders. | ||||
|     /// @param signatures Array of signatures corresponding to each order. | ||||
|     /// @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|     /// @param revertIfIncomplete If true, reverts if this function fails to | ||||
|     ///        fill the full fill amount for any individual order. | ||||
|     /// @return takerTokenFilledAmounts Array of amounts filled, in taker token. | ||||
|     /// @return makerTokenFilledAmounts Array of amounts filled, in maker token. | ||||
|     function batchFillLimitOrders( | ||||
|         LibNativeOrder.LimitOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures, | ||||
|         uint128[] calldata takerTokenFillAmounts, | ||||
|         bool revertIfIncomplete | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         override | ||||
|         returns ( | ||||
|             uint128[] memory takerTokenFilledAmounts, | ||||
|             uint128[] memory makerTokenFilledAmounts | ||||
|         ) | ||||
|     { | ||||
|         require( | ||||
|             orders.length == signatures.length && orders.length == takerTokenFillAmounts.length, | ||||
|             'BatchFillNativeOrdersFeature::batchFillLimitOrders/MISMATCHED_ARRAY_LENGTHS' | ||||
|         ); | ||||
|         takerTokenFilledAmounts = new uint128[](orders.length); | ||||
|         makerTokenFilledAmounts = new uint128[](orders.length); | ||||
|         uint256 protocolFee = uint256(INativeOrdersFeature(address(this)).getProtocolFeeMultiplier()) | ||||
|             .safeMul(tx.gasprice); | ||||
|         uint256 ethProtocolFeePaid; | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             try | ||||
|                 INativeOrdersFeature(address(this))._fillLimitOrder | ||||
|                     ( | ||||
|                         orders[i], | ||||
|                         signatures[i], | ||||
|                         takerTokenFillAmounts[i], | ||||
|                         msg.sender, | ||||
|                         msg.sender | ||||
|                     ) | ||||
|                 returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|             { | ||||
|                 // Update amounts filled. | ||||
|                 (takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) = | ||||
|                     (takerTokenFilledAmount, makerTokenFilledAmount); | ||||
|                 ethProtocolFeePaid = ethProtocolFeePaid.safeAdd(protocolFee); | ||||
|             } catch {} | ||||
|  | ||||
|             if ( | ||||
|                 revertIfIncomplete && | ||||
|                 takerTokenFilledAmounts[i] < takerTokenFillAmounts[i] | ||||
|             ) { | ||||
|                 bytes32 orderHash = _getEIP712Hash( | ||||
|                     LibNativeOrder.getLimitOrderStructHash(orders[i]) | ||||
|                 ); | ||||
|                 // Did not fill the amount requested. | ||||
|                 LibNativeOrdersRichErrors.BatchFillIncompleteError( | ||||
|                     orderHash, | ||||
|                     takerTokenFilledAmounts[i], | ||||
|                     takerTokenFillAmounts[i] | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|         LibNativeOrder.refundExcessProtocolFeeToSender(ethProtocolFeePaid); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fills multiple RFQ orders. | ||||
|     /// @param orders Array of RFQ orders. | ||||
|     /// @param signatures Array of signatures corresponding to each order. | ||||
|     /// @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|     /// @param revertIfIncomplete If true, reverts if this function fails to | ||||
|     ///        fill the full fill amount for any individual order. | ||||
|     /// @return takerTokenFilledAmounts Array of amounts filled, in taker token. | ||||
|     /// @return makerTokenFilledAmounts Array of amounts filled, in maker token. | ||||
|     function batchFillRfqOrders( | ||||
|         LibNativeOrder.RfqOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures, | ||||
|         uint128[] calldata takerTokenFillAmounts, | ||||
|         bool revertIfIncomplete | ||||
|     ) | ||||
|         external | ||||
|         override | ||||
|         returns ( | ||||
|             uint128[] memory takerTokenFilledAmounts, | ||||
|             uint128[] memory makerTokenFilledAmounts | ||||
|         ) | ||||
|     { | ||||
|         require( | ||||
|             orders.length == signatures.length && orders.length == takerTokenFillAmounts.length, | ||||
|             'BatchFillNativeOrdersFeature::batchFillRfqOrders/MISMATCHED_ARRAY_LENGTHS' | ||||
|         ); | ||||
|         takerTokenFilledAmounts = new uint128[](orders.length); | ||||
|         makerTokenFilledAmounts = new uint128[](orders.length); | ||||
|         for (uint256 i = 0; i != orders.length; i++) { | ||||
|             try | ||||
|                 INativeOrdersFeature(address(this))._fillRfqOrder | ||||
|                     ( | ||||
|                         orders[i], | ||||
|                         signatures[i], | ||||
|                         takerTokenFillAmounts[i], | ||||
|                         msg.sender | ||||
|                     ) | ||||
|                 returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|             { | ||||
|                 // Update amounts filled. | ||||
|                 (takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) = | ||||
|                     (takerTokenFilledAmount, makerTokenFilledAmount); | ||||
|             } catch {} | ||||
|  | ||||
|             if ( | ||||
|                 revertIfIncomplete && | ||||
|                 takerTokenFilledAmounts[i] < takerTokenFillAmounts[i] | ||||
|             ) { | ||||
|                 // Did not fill the amount requested. | ||||
|                 bytes32 orderHash = _getEIP712Hash( | ||||
|                     LibNativeOrder.getRfqOrderStructHash(orders[i]) | ||||
|                 ); | ||||
|                 LibNativeOrdersRichErrors.BatchFillIncompleteError( | ||||
|                     orderHash, | ||||
|                     takerTokenFilledAmounts[i], | ||||
|                     takerTokenFillAmounts[i] | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -23,7 +23,7 @@ pragma experimental ABIEncoderV2; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "../migrations/LibBootstrap.sol"; | ||||
| import "../storage/LibProxyStorage.sol"; | ||||
| import "./IBootstrapFeature.sol"; | ||||
| import "./interfaces/IBootstrapFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Detachable `bootstrap()` feature. | ||||
|   | ||||
| @@ -31,8 +31,8 @@ import "../fixins/FixinCommon.sol"; | ||||
| import "../fixins/FixinTokenSpender.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "../transformers/LibERC20Transformer.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./ILiquidityProviderFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/ILiquidityProviderFeature.sol"; | ||||
|  | ||||
|  | ||||
| contract LiquidityProviderFeature is | ||||
|   | ||||
| @@ -30,11 +30,11 @@ import "../fixins/FixinTokenSpender.sol"; | ||||
| import "../fixins/FixinEIP712.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "../storage/LibMetaTransactionsStorage.sol"; | ||||
| import "./IMetaTransactionsFeature.sol"; | ||||
| import "./ITransformERC20Feature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/IMetaTransactionsFeature.sol"; | ||||
| import "./interfaces/INativeOrdersFeature.sol"; | ||||
| import "./interfaces/ITransformERC20Feature.sol"; | ||||
| import "./libs/LibSignature.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./INativeOrdersFeature.sol"; | ||||
|  | ||||
| /// @dev MetaTransactions feature. | ||||
| contract MetaTransactionsFeature is | ||||
|   | ||||
							
								
								
									
										805
									
								
								contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										805
									
								
								contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,805 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "../external/ILiquidityProviderSandbox.sol"; | ||||
| import "../fixins/FixinCommon.sol"; | ||||
| import "../fixins/FixinEIP712.sol"; | ||||
| import "../fixins/FixinTokenSpender.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "../transformers/LibERC20Transformer.sol"; | ||||
| import "../vendor/ILiquidityProvider.sol"; | ||||
| import "../vendor/IUniswapV2Pair.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/IMultiplexFeature.sol"; | ||||
| import "./interfaces/INativeOrdersFeature.sol"; | ||||
| import "./interfaces/ITransformERC20Feature.sol"; | ||||
| import "./libs/LibNativeOrder.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev This feature enables efficient batch and multi-hop trades | ||||
| ///      using different liquidity sources. | ||||
| contract MultiplexFeature is | ||||
|     IFeature, | ||||
|     IMultiplexFeature, | ||||
|     FixinCommon, | ||||
|     FixinEIP712, | ||||
|     FixinTokenSpender | ||||
| { | ||||
|     using LibERC20Transformer for IERC20TokenV06; | ||||
|     using LibSafeMathV06 for uint128; | ||||
|     using LibSafeMathV06 for uint256; | ||||
|  | ||||
|     /// @dev Name of this feature. | ||||
|     string public constant override FEATURE_NAME = "MultiplexFeature"; | ||||
|     /// @dev Version of this feature. | ||||
|     uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); | ||||
|  | ||||
|     /// @dev The WETH token contract. | ||||
|     IEtherTokenV06 private immutable weth; | ||||
|     /// @dev The sandbox contract address. | ||||
|     ILiquidityProviderSandbox public immutable sandbox; | ||||
|     // address of the UniswapV2Factory contract. | ||||
|     address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; | ||||
|     // address of the (Sushiswap) UniswapV2Factory contract. | ||||
|     address private constant SUSHISWAP_FACTORY = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac; | ||||
|     // Init code hash of the UniswapV2Pair contract. | ||||
|     uint256 private constant UNISWAP_PAIR_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f; | ||||
|     // Init code hash of the (Sushiswap) UniswapV2Pair contract. | ||||
|     uint256 private constant SUSHISWAP_PAIR_INIT_CODE_HASH = 0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303; | ||||
|  | ||||
|     constructor( | ||||
|         address zeroExAddress, | ||||
|         IEtherTokenV06 weth_, | ||||
|         ILiquidityProviderSandbox sandbox_, | ||||
|         bytes32 greedyTokensBloomFilter | ||||
|     ) | ||||
|         public | ||||
|         FixinEIP712(zeroExAddress) | ||||
|         FixinTokenSpender(greedyTokensBloomFilter) | ||||
|     { | ||||
|         weth = weth_; | ||||
|         sandbox = sandbox_; | ||||
|     } | ||||
|  | ||||
|     /// @dev Initialize and register this feature. | ||||
|     ///      Should be delegatecalled by `Migrate.migrate()`. | ||||
|     /// @return success `LibMigrate.SUCCESS` on success. | ||||
|     function migrate() | ||||
|         external | ||||
|         returns (bytes4 success) | ||||
|     { | ||||
|         _registerFeatureFunction(this.batchFill.selector); | ||||
|         _registerFeatureFunction(this.multiHopFill.selector); | ||||
|         return LibMigrate.MIGRATE_SUCCESS; | ||||
|     } | ||||
|  | ||||
|     /// @dev Executes a batch of fills selling `fillData.inputToken` | ||||
|     ///      for `fillData.outputToken` in sequence. Refer to the | ||||
|     ///      internal variant `_batchFill` for the allowed nested | ||||
|     ///      operations. | ||||
|     /// @param fillData Encodes the input/output tokens, the sell | ||||
|     ///        amount, and the nested operations for this batch fill. | ||||
|     /// @param minBuyAmount The minimum amount of `fillData.outputToken` | ||||
|     ///        to buy. Reverts if this amount is not met. | ||||
|     /// @return outputTokenAmount The amount of the output token bought. | ||||
|     function batchFill( | ||||
|         BatchFillData memory fillData, | ||||
|         uint256 minBuyAmount | ||||
|     ) | ||||
|         public | ||||
|         payable | ||||
|         override | ||||
|         returns (uint256 outputTokenAmount) | ||||
|     { | ||||
|         // Cache the sender's balance of the output token. | ||||
|         outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender); | ||||
|         // Cache the contract's ETH balance prior to this call. | ||||
|         uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value); | ||||
|  | ||||
|         // Perform the batch fill. | ||||
|         _batchFill(fillData); | ||||
|  | ||||
|         // The `outputTokenAmount` returned by `_batchFill` may not | ||||
|         // be fully accurate (e.g. due to some janky token). | ||||
|         outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender) | ||||
|             .safeSub(outputTokenAmount); | ||||
|         require( | ||||
|             outputTokenAmount >= minBuyAmount, | ||||
|             "MultiplexFeature::batchFill/UNDERBOUGHT" | ||||
|         ); | ||||
|  | ||||
|         uint256 ethBalanceAfter = address(this).balance; | ||||
|         require( | ||||
|             ethBalanceAfter >= ethBalanceBefore, | ||||
|             "MultiplexFeature::batchFill/OVERSPENT_ETH" | ||||
|         ); | ||||
|         // Refund ETH | ||||
|         if (ethBalanceAfter > ethBalanceBefore) { | ||||
|             _transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Executes a sequence of fills "hopping" through the | ||||
|     ///      path of tokens given by `fillData.tokens`. Refer to the | ||||
|     ///      internal variant `_multiHopFill` for the allowed nested | ||||
|     ///      operations. | ||||
|     /// @param fillData Encodes the path of tokens, the sell amount, | ||||
|     ///        and the nested operations for this multi-hop fill. | ||||
|     /// @param minBuyAmount The minimum amount of the output token | ||||
|     ///        to buy. Reverts if this amount is not met. | ||||
|     /// @return outputTokenAmount The amount of the output token bought. | ||||
|     function multiHopFill( | ||||
|         MultiHopFillData memory fillData, | ||||
|         uint256 minBuyAmount | ||||
|     ) | ||||
|         public | ||||
|         payable | ||||
|         override | ||||
|         returns (uint256 outputTokenAmount) | ||||
|     { | ||||
|         IERC20TokenV06 outputToken = IERC20TokenV06(fillData.tokens[fillData.tokens.length - 1]); | ||||
|         // Cache the sender's balance of the output token. | ||||
|         outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender); | ||||
|         // Cache the contract's ETH balance prior to this call. | ||||
|         uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value); | ||||
|  | ||||
|         // Perform the multi-hop fill. Pass in `msg.value` as the maximum | ||||
|         // allowable amount of ETH for the wrapped calls to consume. | ||||
|         _multiHopFill(fillData, msg.value); | ||||
|  | ||||
|         // The `outputTokenAmount` returned by `_multiHopFill` may not | ||||
|         // be fully accurate (e.g. due to some janky token). | ||||
|         outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender) | ||||
|             .safeSub(outputTokenAmount); | ||||
|         require( | ||||
|             outputTokenAmount >= minBuyAmount, | ||||
|             "MultiplexFeature::multiHopFill/UNDERBOUGHT" | ||||
|         ); | ||||
|  | ||||
|         uint256 ethBalanceAfter = address(this).balance; | ||||
|         require( | ||||
|             ethBalanceAfter >= ethBalanceBefore, | ||||
|             "MultiplexFeature::multiHopFill/OVERSPENT_ETH" | ||||
|         ); | ||||
|         // Refund ETH | ||||
|         if (ethBalanceAfter > ethBalanceBefore) { | ||||
|             _transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Similar to FQT. If `fillData.sellAmount` is set to `type(uint256).max`, | ||||
|     // this is effectively a batch fill. Otherwise it can be set to perform a | ||||
|     // market sell of some amount. Note that the `outputTokenAmount` returned | ||||
|     // by this function could theoretically be inaccurate if `msg.sender` has | ||||
|     // set a token allowance on an external contract that gets called during | ||||
|     // the execution of this function. | ||||
|     function _batchFill(BatchFillData memory fillData) | ||||
|         internal | ||||
|         returns (uint256 outputTokenAmount, uint256 remainingEth) | ||||
|     { | ||||
|         // Track the remaining ETH allocated to this call. | ||||
|         remainingEth = msg.value; | ||||
|         // Track the amount of input token sold. | ||||
|         uint256 soldAmount; | ||||
|         for (uint256 i = 0; i != fillData.calls.length; i++) { | ||||
|             // Check if we've hit our target. | ||||
|             if (soldAmount >= fillData.sellAmount) { break; } | ||||
|             WrappedBatchCall memory wrappedCall = fillData.calls[i]; | ||||
|             // Compute the fill amount. | ||||
|             uint256 inputTokenAmount = LibSafeMathV06.min256( | ||||
|                 wrappedCall.sellAmount, | ||||
|                 fillData.sellAmount.safeSub(soldAmount) | ||||
|             ); | ||||
|             if (wrappedCall.selector == INativeOrdersFeature._fillRfqOrder.selector) { | ||||
|                 // Decode the RFQ order and signature. | ||||
|                 ( | ||||
|                     LibNativeOrder.RfqOrder memory order, | ||||
|                     LibSignature.Signature memory signature | ||||
|                 ) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (LibNativeOrder.RfqOrder, LibSignature.Signature) | ||||
|                 ); | ||||
|                 if (order.expiry <= uint64(block.timestamp)) { | ||||
|                     bytes32 orderHash = _getEIP712Hash( | ||||
|                         LibNativeOrder.getRfqOrderStructHash(order) | ||||
|                     ); | ||||
|                     emit ExpiredRfqOrder( | ||||
|                         orderHash, | ||||
|                         order.maker, | ||||
|                         order.expiry | ||||
|                     ); | ||||
|                     continue; | ||||
|                 } | ||||
|                 require( | ||||
|                     order.takerToken == fillData.inputToken && | ||||
|                     order.makerToken == fillData.outputToken, | ||||
|                     "MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS" | ||||
|                 ); | ||||
|                 // Try filling the RFQ order. Swallows reverts. | ||||
|                 try | ||||
|                     INativeOrdersFeature(address(this))._fillRfqOrder | ||||
|                         ( | ||||
|                             order, | ||||
|                             signature, | ||||
|                             inputTokenAmount.safeDowncastToUint128(), | ||||
|                             msg.sender | ||||
|                         ) | ||||
|                     returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|                 { | ||||
|                     // Increment the sold and bought amounts. | ||||
|                     soldAmount = soldAmount.safeAdd(takerTokenFilledAmount); | ||||
|                     outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount); | ||||
|                 } catch {} | ||||
|             } else if (wrappedCall.selector == this._sellToUniswap.selector) { | ||||
|                 (address[] memory tokens, bool isSushi) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (address[], bool) | ||||
|                 ); | ||||
|                 require( | ||||
|                     tokens.length >= 2 && | ||||
|                     tokens[0] == address(fillData.inputToken) && | ||||
|                     tokens[tokens.length - 1] == address(fillData.outputToken), | ||||
|                     "MultiplexFeature::_batchFill/UNISWAP_INVALID_TOKENS" | ||||
|                 ); | ||||
|                 // Perform the Uniswap/Sushiswap trade. | ||||
|                 uint256 outputTokenAmount_  = _sellToUniswap( | ||||
|                     tokens, | ||||
|                     inputTokenAmount, | ||||
|                     isSushi, | ||||
|                     address(0), | ||||
|                     msg.sender | ||||
|                 ); | ||||
|                 // Increment the sold and bought amounts. | ||||
|                 soldAmount = soldAmount.safeAdd(inputTokenAmount); | ||||
|                 outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_); | ||||
|             } else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) { | ||||
|                 (address provider, bytes memory auxiliaryData) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (address, bytes) | ||||
|                 ); | ||||
|                 if (fillData.inputToken.isTokenETH()) { | ||||
|                     inputTokenAmount = LibSafeMathV06.min256( | ||||
|                         inputTokenAmount, | ||||
|                         remainingEth | ||||
|                     ); | ||||
|                     // Transfer the input ETH to the provider. | ||||
|                     _transferEth(payable(provider), inputTokenAmount); | ||||
|                     // Count that ETH as spent. | ||||
|                     remainingEth -= inputTokenAmount; | ||||
|                 } else { | ||||
|                     // Transfer input ERC20 tokens to the provider. | ||||
|                     _transferERC20Tokens( | ||||
|                         fillData.inputToken, | ||||
|                         msg.sender, | ||||
|                         provider, | ||||
|                         inputTokenAmount | ||||
|                     ); | ||||
|                 } | ||||
|                 // Perform the PLP trade. | ||||
|                 uint256 outputTokenAmount_ = _sellToLiquidityProvider( | ||||
|                     fillData.inputToken, | ||||
|                     fillData.outputToken, | ||||
|                     inputTokenAmount, | ||||
|                     ILiquidityProvider(provider), | ||||
|                     msg.sender, | ||||
|                     auxiliaryData | ||||
|                 ); | ||||
|                 // Increment the sold and bought amounts. | ||||
|                 soldAmount = soldAmount.safeAdd(inputTokenAmount); | ||||
|                 outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_); | ||||
|             } else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) { | ||||
|                 ITransformERC20Feature.TransformERC20Args memory args; | ||||
|                 args.taker = msg.sender; | ||||
|                 args.inputToken = fillData.inputToken; | ||||
|                 args.outputToken = fillData.outputToken; | ||||
|                 args.inputTokenAmount = inputTokenAmount; | ||||
|                 args.minOutputTokenAmount = 0; | ||||
|                 uint256 ethValue; | ||||
|                 (args.transformations, ethValue) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (ITransformERC20Feature.Transformation[], uint256) | ||||
|                 ); | ||||
|                 // Do not spend more than the remaining ETH. | ||||
|                 ethValue = LibSafeMathV06.min256( | ||||
|                     ethValue, | ||||
|                     remainingEth | ||||
|                 ); | ||||
|                 if (ethValue > 0) { | ||||
|                     require( | ||||
|                         args.inputToken.isTokenETH(), | ||||
|                         "MultiplexFeature::_batchFill/ETH_TRANSFORM_ONLY" | ||||
|                     ); | ||||
|                 } | ||||
|                 try ITransformERC20Feature(address(this))._transformERC20 | ||||
|                     {value: ethValue} | ||||
|                     (args) | ||||
|                     returns (uint256 outputTokenAmount_) | ||||
|                 { | ||||
|                     remainingEth -= ethValue; | ||||
|                     soldAmount = soldAmount.safeAdd(inputTokenAmount); | ||||
|                     outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_); | ||||
|                 } catch {} | ||||
|             } else if (wrappedCall.selector == this._multiHopFill.selector) { | ||||
|                 MultiHopFillData memory multiHopFillData; | ||||
|                 uint256 ethValue; | ||||
|                 ( | ||||
|                     multiHopFillData.tokens, | ||||
|                     multiHopFillData.calls, | ||||
|                     ethValue | ||||
|                 ) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (address[], WrappedMultiHopCall[], uint256) | ||||
|                 ); | ||||
|                 multiHopFillData.sellAmount = inputTokenAmount; | ||||
|                 // Do not spend more than the remaining ETH. | ||||
|                 ethValue = LibSafeMathV06.min256( | ||||
|                     ethValue, | ||||
|                     remainingEth | ||||
|                 ); | ||||
|                 // Subtract the ethValue allocated to the nested multi-hop fill. | ||||
|                 remainingEth -= ethValue; | ||||
|                 (uint256 outputTokenAmount_, uint256 leftoverEth) = | ||||
|                     _multiHopFill(multiHopFillData, ethValue); | ||||
|                 // Increment the sold and bought amounts. | ||||
|                 soldAmount = soldAmount.safeAdd(inputTokenAmount); | ||||
|                 outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_); | ||||
|                 // Add back any ETH that wasn't used by the nested multi-hop fill. | ||||
|                 remainingEth += leftoverEth; | ||||
|             } else { | ||||
|                 revert("MultiplexFeature::_batchFill/UNRECOGNIZED_SELECTOR"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Internal variant of `multiHopFill`. This function can be nested within | ||||
|     // a `_batchFill`. | ||||
|     // This function executes a sequence of fills "hopping" through the | ||||
|     // path of tokens given by `fillData.tokens`. The nested operations that | ||||
|     // can be used as "hops" are: | ||||
|     // - WETH.deposit (wraps ETH) | ||||
|     // - WETH.withdraw (unwraps WETH) | ||||
|     // - _sellToUniswap (executes a Uniswap/Sushiswap swap) | ||||
|     // - _sellToLiquidityProvider (executes a PLP swap) | ||||
|     // - _transformERC20 (executes arbitrary ERC20 Transformations) | ||||
|     // This function optimizes the number of ERC20 transfers performed | ||||
|     // by having each hop transfer its output tokens directly to the | ||||
|     // target address of the next hop. Note that the `outputTokenAmount` returned | ||||
|     // by this function could theoretically be inaccurate if `msg.sender` has | ||||
|     // set a token allowance on an external contract that gets called during | ||||
|     // the execution of this function. | ||||
|     function _multiHopFill(MultiHopFillData memory fillData, uint256 totalEth) | ||||
|         public | ||||
|         returns (uint256 outputTokenAmount, uint256 remainingEth) | ||||
|     { | ||||
|         // There should be one call/hop between every two tokens | ||||
|         // in the path. | ||||
|         // tokens[0]––calls[0]––>tokens[1]––...––calls[n-1]––>tokens[n] | ||||
|         require( | ||||
|             fillData.tokens.length == fillData.calls.length + 1, | ||||
|             "MultiplexFeature::_multiHopFill/MISMATCHED_ARRAY_LENGTHS" | ||||
|         ); | ||||
|         // Track the remaining ETH allocated to this call. | ||||
|         remainingEth = totalEth; | ||||
|         // This variable is used as the input and output amounts of | ||||
|         // each hop. After the final hop, this will contain the output | ||||
|         // amount of the multi-hop fill. | ||||
|         outputTokenAmount = fillData.sellAmount; | ||||
|         // This variable is used to cache the address to target in the | ||||
|         // next hop. See `_computeHopRecipient` for details. | ||||
|         address nextTarget; | ||||
|         for (uint256 i = 0; i != fillData.calls.length; i++) { | ||||
|             WrappedMultiHopCall memory wrappedCall = fillData.calls[i]; | ||||
|             if (wrappedCall.selector == this._sellToUniswap.selector) { | ||||
|                 // If the next hop supports a "transfer then execute" pattern, | ||||
|                 // the recipient will not be `msg.sender`. See `_computeHopRecipient` | ||||
|                 // for details. | ||||
|                 address recipient = _computeHopRecipient(fillData.calls, i); | ||||
|                 (address[] memory tokens, bool isSushi) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (address[], bool) | ||||
|                 ); | ||||
|                 // Perform the Uniswap/Sushiswap trade. | ||||
|                 outputTokenAmount = _sellToUniswap( | ||||
|                     tokens, | ||||
|                     outputTokenAmount, | ||||
|                     isSushi, | ||||
|                     nextTarget, | ||||
|                     recipient | ||||
|                 ); | ||||
|                 // If the recipient was not `msg.sender`, it must be the target | ||||
|                 // contract for the next hop. | ||||
|                 nextTarget = recipient == msg.sender ? address(0) : recipient; | ||||
|             } else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) { | ||||
|                 // If the next hop supports a "transfer then execute" pattern, | ||||
|                 // the recipient will not be `msg.sender`. See `_computeHopRecipient` | ||||
|                 // for details. | ||||
|                 address recipient = _computeHopRecipient(fillData.calls, i); | ||||
|                 // If `nextTarget` was not set in the previous hop, then we | ||||
|                 // need to send in the input ETH/tokens to the liquidity provider | ||||
|                 // contract before executing the trade. | ||||
|                 if (nextTarget == address(0)) { | ||||
|                     (address provider, bytes memory auxiliaryData) = abi.decode( | ||||
|                         wrappedCall.data, | ||||
|                         (address, bytes) | ||||
|                     ); | ||||
|                     // Transfer input ETH or ERC20 tokens to the liquidity | ||||
|                     // provider contract. | ||||
|                     if (IERC20TokenV06(fillData.tokens[i]).isTokenETH()) { | ||||
|                         outputTokenAmount = LibSafeMathV06.min256( | ||||
|                             outputTokenAmount, | ||||
|                             remainingEth | ||||
|                         ); | ||||
|                         _transferEth(payable(provider), outputTokenAmount); | ||||
|                         remainingEth -= outputTokenAmount; | ||||
|                     } else { | ||||
|                         _transferERC20Tokens( | ||||
|                             IERC20TokenV06(fillData.tokens[i]), | ||||
|                             msg.sender, | ||||
|                             provider, | ||||
|                             outputTokenAmount | ||||
|                         ); | ||||
|                     } | ||||
|                     outputTokenAmount = _sellToLiquidityProvider( | ||||
|                         IERC20TokenV06(fillData.tokens[i]), | ||||
|                         IERC20TokenV06(fillData.tokens[i + 1]), | ||||
|                         outputTokenAmount, | ||||
|                         ILiquidityProvider(provider), | ||||
|                         recipient, | ||||
|                         auxiliaryData | ||||
|                     ); | ||||
|                 } else { | ||||
|                     (, bytes memory auxiliaryData) = abi.decode( | ||||
|                         wrappedCall.data, | ||||
|                         (address, bytes) | ||||
|                     ); | ||||
|                     // Tokens and ETH have already been transferred to | ||||
|                     // the liquidity provider contract in the previous hop. | ||||
|                     outputTokenAmount = _sellToLiquidityProvider( | ||||
|                         IERC20TokenV06(fillData.tokens[i]), | ||||
|                         IERC20TokenV06(fillData.tokens[i + 1]), | ||||
|                         outputTokenAmount, | ||||
|                         ILiquidityProvider(nextTarget), | ||||
|                         recipient, | ||||
|                         auxiliaryData | ||||
|                     ); | ||||
|                 } | ||||
|                 // If the recipient was not `msg.sender`, it must be the target | ||||
|                 // contract for the next hop. | ||||
|                 nextTarget = recipient == msg.sender ? address(0) : recipient; | ||||
|             } else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) { | ||||
|                 ITransformERC20Feature.TransformERC20Args memory args; | ||||
|                 args.inputToken = IERC20TokenV06(fillData.tokens[i]); | ||||
|                 args.outputToken = IERC20TokenV06(fillData.tokens[i + 1]); | ||||
|                 args.minOutputTokenAmount = 0; | ||||
|                 args.taker = payable(_computeHopRecipient(fillData.calls, i)); | ||||
|                 if (nextTarget != address(0)) { | ||||
|                     // If `nextTarget` was set in the previous hop, then the input | ||||
|                     // token was already sent to the FlashWallet. Setting | ||||
|                     // `inputTokenAmount` to 0 indicates that no tokens need to | ||||
|                     // be pulled into the FlashWallet before executing the | ||||
|                     // transformations. | ||||
|                     args.inputTokenAmount = 0; | ||||
|                 } else if ( | ||||
|                     args.taker != msg.sender && | ||||
|                     !args.inputToken.isTokenETH() | ||||
|                 ) { | ||||
|                     address flashWallet = address( | ||||
|                         ITransformERC20Feature(address(this)).getTransformWallet() | ||||
|                     ); | ||||
|                     // The input token has _not_ already been sent to the | ||||
|                     // FlashWallet. We also want PayTakerTransformer to | ||||
|                     // send the output token to some address other than | ||||
|                     // msg.sender, so we must transfer the input token | ||||
|                     // to the FlashWallet here. | ||||
|                     _transferERC20Tokens( | ||||
|                         args.inputToken, | ||||
|                         msg.sender, | ||||
|                         flashWallet, | ||||
|                         outputTokenAmount | ||||
|                     ); | ||||
|                     args.inputTokenAmount = 0; | ||||
|                 } else { | ||||
|                     // Otherwise, either: | ||||
|                     // (1) args.taker == msg.sender, in which case | ||||
|                     //     `_transformERC20` will pull the input token | ||||
|                     //     into the FlashWallet, or | ||||
|                     // (2) args.inputToken == ETH_TOKEN_ADDRESS, in which | ||||
|                     //     case ETH is attached to the call and no token | ||||
|                     //     transfer occurs. | ||||
|                     args.inputTokenAmount = outputTokenAmount; | ||||
|                 } | ||||
|                 uint256 ethValue; | ||||
|                 (args.transformations, ethValue) = abi.decode( | ||||
|                     wrappedCall.data, | ||||
|                     (ITransformERC20Feature.Transformation[], uint256) | ||||
|                 ); | ||||
|                 // Do not spend more than the remaining ETH. | ||||
|                 ethValue = LibSafeMathV06.min256(ethValue, remainingEth); | ||||
|                 if (ethValue > 0) { | ||||
|                     require( | ||||
|                         args.inputToken.isTokenETH(), | ||||
|                         "MultiplexFeature::_multiHopFill/ETH_TRANSFORM_ONLY" | ||||
|                     ); | ||||
|                 } | ||||
|                 // Call `_transformERC20`. | ||||
|                 outputTokenAmount = ITransformERC20Feature(address(this)) | ||||
|                     ._transformERC20{value: ethValue}(args); | ||||
|                 // Decrement the remaining ETH. | ||||
|                 remainingEth -= ethValue; | ||||
|                 // If the recipient was not `msg.sender`, it must be the target | ||||
|                 // contract for the next hop. | ||||
|                 nextTarget = args.taker == msg.sender ? address(0) : args.taker; | ||||
|             } else if (wrappedCall.selector == IEtherTokenV06.deposit.selector) { | ||||
|                 require( | ||||
|                     i == 0, | ||||
|                     "MultiplexFeature::_multiHopFill/DEPOSIT_FIRST_HOP_ONLY" | ||||
|                 ); | ||||
|                 uint256 ethValue = LibSafeMathV06.min256(outputTokenAmount, remainingEth); | ||||
|                 // Wrap ETH. | ||||
|                 weth.deposit{value: ethValue}(); | ||||
|                 nextTarget = _computeHopRecipient(fillData.calls, i); | ||||
|                 weth.transfer(nextTarget, ethValue); | ||||
|                 remainingEth -= ethValue; | ||||
|             } else if (wrappedCall.selector == IEtherTokenV06.withdraw.selector) { | ||||
|                 require( | ||||
|                     i == fillData.calls.length - 1, | ||||
|                     "MultiplexFeature::_multiHopFill/WITHDRAW_LAST_HOP_ONLY" | ||||
|                 ); | ||||
|                 // Unwrap WETH and send to `msg.sender`. | ||||
|                 weth.withdraw(outputTokenAmount); | ||||
|                 _transferEth(msg.sender, outputTokenAmount); | ||||
|                 nextTarget = address(0); | ||||
|             } else { | ||||
|                 revert("MultiplexFeature::_multiHopFill/UNRECOGNIZED_SELECTOR"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Similar to the UniswapFeature, but with a couple of differences: | ||||
|     // - Does not perform the transfer in if `pairAddress` is given, | ||||
|     //   which indicates that the transfer in was already performed | ||||
|     //   in the previous hop of a multi-hop fill. | ||||
|     // - Does not include a minBuyAmount check (which is performed in | ||||
|     //   either `batchFill` or `multiHopFill`). | ||||
|     // - Takes a `recipient` address parameter, so the output of the | ||||
|     //   final `swap` call can be sent to an address other than `msg.sender`. | ||||
|     function _sellToUniswap( | ||||
|         address[] memory tokens, | ||||
|         uint256 sellAmount, | ||||
|         bool isSushi, | ||||
|         address pairAddress, | ||||
|         address recipient | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256 outputTokenAmount) | ||||
|     { | ||||
|         require(tokens.length > 1, "MultiplexFeature::_sellToUniswap/InvalidTokensLength"); | ||||
|  | ||||
|         if (pairAddress == address(0)) { | ||||
|             pairAddress = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi); | ||||
|             _transferERC20Tokens( | ||||
|                 IERC20TokenV06(tokens[0]), | ||||
|                 msg.sender, | ||||
|                 pairAddress, | ||||
|                 sellAmount | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < tokens.length - 1; i++) { | ||||
|             (address inputToken, address outputToken) = (tokens[i], tokens[i + 1]); | ||||
|             outputTokenAmount = _computeUniswapOutputAmount( | ||||
|                 pairAddress, | ||||
|                 inputToken, | ||||
|                 outputToken, | ||||
|                 sellAmount | ||||
|             ); | ||||
|             (uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken | ||||
|                 ? (uint256(0), outputTokenAmount) | ||||
|                 : (outputTokenAmount, uint256(0)); | ||||
|             address to = i < tokens.length - 2 | ||||
|                 ? _computeUniswapPairAddress(outputToken, tokens[i + 2], isSushi) | ||||
|                 : recipient; | ||||
|             IUniswapV2Pair(pairAddress).swap( | ||||
|                 amount0Out, | ||||
|                 amount1Out, | ||||
|                 to, | ||||
|                 new bytes(0) | ||||
|             ); | ||||
|             pairAddress = to; | ||||
|             sellAmount = outputTokenAmount; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Same as the LiquidityProviderFeature, but without the transfer in | ||||
|     // (which is potentially done in the previous hop of a multi-hop fill) | ||||
|     // and without the minBuyAmount check (which is performed at the top, i.e. | ||||
|     // in either `batchFill` or `multiHopFill`). | ||||
|     function _sellToLiquidityProvider( | ||||
|         IERC20TokenV06 inputToken, | ||||
|         IERC20TokenV06 outputToken, | ||||
|         uint256 inputTokenAmount, | ||||
|         ILiquidityProvider provider, | ||||
|         address recipient, | ||||
|         bytes memory auxiliaryData | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256 outputTokenAmount) | ||||
|     { | ||||
|         uint256 balanceBefore = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient); | ||||
|         if (IERC20TokenV06(inputToken).isTokenETH()) { | ||||
|             sandbox.executeSellEthForToken( | ||||
|                 provider, | ||||
|                 outputToken, | ||||
|                 recipient, | ||||
|                 0, | ||||
|                 auxiliaryData | ||||
|             ); | ||||
|         } else if (IERC20TokenV06(outputToken).isTokenETH()) { | ||||
|             sandbox.executeSellTokenForEth( | ||||
|                 provider, | ||||
|                 inputToken, | ||||
|                 recipient, | ||||
|                 0, | ||||
|                 auxiliaryData | ||||
|             ); | ||||
|         } else { | ||||
|             sandbox.executeSellTokenForToken( | ||||
|                 provider, | ||||
|                 inputToken, | ||||
|                 outputToken, | ||||
|                 recipient, | ||||
|                 0, | ||||
|                 auxiliaryData | ||||
|             ); | ||||
|         } | ||||
|         outputTokenAmount = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient) | ||||
|             .safeSub(balanceBefore); | ||||
|         emit LiquidityProviderSwap( | ||||
|             address(inputToken), | ||||
|             address(outputToken), | ||||
|             inputTokenAmount, | ||||
|             outputTokenAmount, | ||||
|             address(provider), | ||||
|             recipient | ||||
|         ); | ||||
|         return outputTokenAmount; | ||||
|     } | ||||
|  | ||||
|     function _transferEth(address payable recipient, uint256 amount) | ||||
|         private | ||||
|     { | ||||
|         (bool success,) = recipient.call{value: amount}(""); | ||||
|         require(success, "MultiplexFeature::_transferEth/TRANSFER_FALIED"); | ||||
|     } | ||||
|  | ||||
|     // Some liquidity sources (e.g. Uniswap, Sushiswap, and PLP) can be passed | ||||
|     // a `recipient` parameter so the boguht tokens are transferred to the | ||||
|     // `recipient` address rather than `msg.sender`. | ||||
|     // Some liquidity sources (also Uniswap, Sushiswap, and PLP incidentally) | ||||
|     // support a "transfer then execute" pattern, where the token being sold | ||||
|     // can be transferred into the contract before calling a swap function to | ||||
|     // execute the trade. | ||||
|     // If the current hop in a multi-hop fill satisfies the first condition, | ||||
|     // and the next hop satisfies the second condition, the tokens bought | ||||
|     // in the current hop can be directly sent to the target contract of | ||||
|     // the next hop to save a transfer. | ||||
|     function _computeHopRecipient( | ||||
|         WrappedMultiHopCall[] memory calls, | ||||
|         uint256 i | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (address recipient) | ||||
|     { | ||||
|         recipient = msg.sender; | ||||
|         if (i < calls.length - 1) { | ||||
|             WrappedMultiHopCall memory nextCall = calls[i + 1]; | ||||
|             if (nextCall.selector == this._sellToUniswap.selector) { | ||||
|                 (address[] memory tokens, bool isSushi) = abi.decode( | ||||
|                     nextCall.data, | ||||
|                     (address[], bool) | ||||
|                 ); | ||||
|                 recipient = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi); | ||||
|             } else if (nextCall.selector == this._sellToLiquidityProvider.selector) { | ||||
|                 (recipient,) = abi.decode( | ||||
|                     nextCall.data, | ||||
|                     (address, bytes) | ||||
|                 ); | ||||
|             } else if (nextCall.selector == IEtherTokenV06.withdraw.selector) { | ||||
|                 recipient = address(this); | ||||
|             } else if (nextCall.selector == ITransformERC20Feature._transformERC20.selector) { | ||||
|                 recipient = address( | ||||
|                     ITransformERC20Feature(address(this)).getTransformWallet() | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|         require( | ||||
|             recipient != address(0), | ||||
|             "MultiplexFeature::_computeHopRecipient/RECIPIENT_IS_NULL" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // Computes the the amount of output token that would be bought | ||||
|     // from Uniswap/Sushiswap given the input amount. | ||||
|     function _computeUniswapOutputAmount( | ||||
|         address pairAddress, | ||||
|         address inputToken, | ||||
|         address outputToken, | ||||
|         uint256 inputAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 outputAmount) | ||||
|     { | ||||
|         require( | ||||
|             inputAmount > 0, | ||||
|             "MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_INPUT_AMOUNT" | ||||
|         ); | ||||
|         (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairAddress).getReserves(); | ||||
|         require( | ||||
|             reserve0 > 0 && reserve1 > 0, | ||||
|             'MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_LIQUIDITY' | ||||
|         ); | ||||
|         (uint256 inputReserve, uint256 outputReserve) = inputToken < outputToken | ||||
|             ? (reserve0, reserve1) | ||||
|             : (reserve1, reserve0); | ||||
|         uint256 inputAmountWithFee = inputAmount.safeMul(997); | ||||
|         uint256 numerator = inputAmountWithFee.safeMul(outputReserve); | ||||
|         uint256 denominator = inputReserve.safeMul(1000).safeAdd(inputAmountWithFee); | ||||
|         return numerator / denominator; | ||||
|     } | ||||
|  | ||||
|     // Computes the Uniswap/Sushiswap pair contract address for the | ||||
|     // given tokens. | ||||
|     function _computeUniswapPairAddress( | ||||
|         address tokenA, | ||||
|         address tokenB, | ||||
|         bool isSushi | ||||
|     ) | ||||
|         private | ||||
|         pure | ||||
|         returns (address pairAddress) | ||||
|     { | ||||
|         (address token0, address token1) = tokenA < tokenB | ||||
|             ? (tokenA, tokenB) | ||||
|             : (tokenB, tokenA); | ||||
|         if (isSushi) { | ||||
|             return address(uint256(keccak256(abi.encodePacked( | ||||
|                 hex'ff', | ||||
|                 SUSHISWAP_FACTORY, | ||||
|                 keccak256(abi.encodePacked(token0, token1)), | ||||
|                 SUSHISWAP_PAIR_INIT_CODE_HASH | ||||
|             )))); | ||||
|         } else { | ||||
|             return address(uint256(keccak256(abi.encodePacked( | ||||
|                 hex'ff', | ||||
|                 UNISWAP_FACTORY, | ||||
|                 keccak256(abi.encodePacked(token0, token1)), | ||||
|                 UNISWAP_PAIR_INIT_CODE_HASH | ||||
|             )))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -26,8 +26,8 @@ import "../errors/LibOwnableRichErrors.sol"; | ||||
| import "../storage/LibOwnableStorage.sol"; | ||||
| import "../migrations/LibBootstrap.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./IOwnableFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/IOwnableFeature.sol"; | ||||
| import "./SimpleFunctionRegistryFeature.sol"; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,8 @@ import "../storage/LibProxyStorage.sol"; | ||||
| import "../storage/LibSimpleFunctionRegistryStorage.sol"; | ||||
| import "../errors/LibSimpleFunctionRegistryRichErrors.sol"; | ||||
| import "../migrations/LibBootstrap.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./ISimpleFunctionRegistryFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/ISimpleFunctionRegistryFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Basic registry management features. | ||||
|   | ||||
| @@ -29,8 +29,8 @@ import "../fixins/FixinCommon.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "../external/IAllowanceTarget.sol"; | ||||
| import "../storage/LibTokenSpenderStorage.sol"; | ||||
| import "./ITokenSpenderFeature.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/ITokenSpenderFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Feature that allows spending token allowances. | ||||
|   | ||||
| @@ -33,8 +33,8 @@ import "../external/FlashWallet.sol"; | ||||
| import "../storage/LibTransformERC20Storage.sol"; | ||||
| import "../transformers/IERC20Transformer.sol"; | ||||
| import "../transformers/LibERC20Transformer.sol"; | ||||
| import "./ITransformERC20Feature.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/ITransformERC20Feature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Feature to composably transform between ERC20 tokens. | ||||
| @@ -313,7 +313,7 @@ contract TransformERC20Feature is | ||||
|             to.transfer(msg.value); | ||||
|         } | ||||
|         // Transfer input tokens. | ||||
|         if (!LibERC20Transformer.isTokenETH(inputToken)) { | ||||
|         if (!LibERC20Transformer.isTokenETH(inputToken) && amount != 0) { | ||||
|             // Token is not ETH, so pull ERC20 tokens. | ||||
|             _transferERC20Tokens( | ||||
|                 inputToken, | ||||
|   | ||||
| @@ -25,8 +25,8 @@ import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "../migrations/LibMigrate.sol"; | ||||
| import "../external/IAllowanceTarget.sol"; | ||||
| import "../fixins/FixinCommon.sol"; | ||||
| import "./IFeature.sol"; | ||||
| import "./IUniswapFeature.sol"; | ||||
| import "./interfaces/IFeature.sol"; | ||||
| import "./interfaces/IUniswapFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev VIP uniswap fill functions. | ||||
| @@ -380,7 +380,7 @@ contract UniswapFeature is | ||||
|                 // will eat all our gas. | ||||
|                 if isTokenPossiblyGreedy(token) { | ||||
|                     // Check if we have enough direct allowance by calling | ||||
|                     // `token.allowance()`` | ||||
|                     // `token.allowance()` | ||||
|                     mstore(0xB00, ALLOWANCE_CALL_SELECTOR_32) | ||||
|                     mstore(0xB04, caller()) | ||||
|                     mstore(0xB24, address()) | ||||
|   | ||||
| @@ -0,0 +1,70 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 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 "../libs/LibNativeOrder.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Feature for batch/market filling limit and RFQ orders. | ||||
| interface IBatchFillNativeOrdersFeature { | ||||
|  | ||||
|     /// @dev Fills multiple limit orders. | ||||
|     /// @param orders Array of limit orders. | ||||
|     /// @param signatures Array of signatures corresponding to each order. | ||||
|     /// @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|     /// @param revertIfIncomplete If true, reverts if this function fails to | ||||
|     ///        fill the full fill amount for any individual order. | ||||
|     /// @return takerTokenFilledAmounts Array of amounts filled, in taker token. | ||||
|     /// @return makerTokenFilledAmounts Array of amounts filled, in maker token. | ||||
|     function batchFillLimitOrders( | ||||
|         LibNativeOrder.LimitOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures, | ||||
|         uint128[] calldata takerTokenFillAmounts, | ||||
|         bool revertIfIncomplete | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns ( | ||||
|             uint128[] memory takerTokenFilledAmounts, | ||||
|             uint128[] memory makerTokenFilledAmounts | ||||
|         ); | ||||
|  | ||||
|     /// @dev Fills multiple RFQ orders. | ||||
|     /// @param orders Array of RFQ orders. | ||||
|     /// @param signatures Array of signatures corresponding to each order. | ||||
|     /// @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|     /// @param revertIfIncomplete If true, reverts if this function fails to | ||||
|     ///        fill the full fill amount for any individual order. | ||||
|     /// @return takerTokenFilledAmounts Array of amounts filled, in taker token. | ||||
|     /// @return makerTokenFilledAmounts Array of amounts filled, in maker token. | ||||
|     function batchFillRfqOrders( | ||||
|         LibNativeOrder.RfqOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures, | ||||
|         uint128[] calldata takerTokenFillAmounts, | ||||
|         bool revertIfIncomplete | ||||
|     ) | ||||
|         external | ||||
|         returns ( | ||||
|             uint128[] memory takerTokenFilledAmounts, | ||||
|             uint128[] memory makerTokenFilledAmounts | ||||
|         ); | ||||
| } | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "../vendor/ILiquidityProvider.sol"; | ||||
| import "../../vendor/ILiquidityProvider.sol"; | ||||
| 
 | ||||
| 
 | ||||
| /// @dev Feature to swap directly with an on-chain liquidity provider. | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "./libs/LibSignature.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| 
 | ||||
| /// @dev Meta-transactions feature. | ||||
| interface IMetaTransactionsFeature { | ||||
| @@ -0,0 +1,117 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
|  | ||||
|  | ||||
| interface IMultiplexFeature { | ||||
|  | ||||
|     // Parameters for `batchFill`. | ||||
|     struct BatchFillData { | ||||
|         // The token being sold. | ||||
|         IERC20TokenV06 inputToken; | ||||
|         // The token being bought. | ||||
|         IERC20TokenV06 outputToken; | ||||
|         // The amount of `inputToken` to sell. | ||||
|         uint256 sellAmount; | ||||
|         // The nested calls to perform. | ||||
|         WrappedBatchCall[] calls; | ||||
|     } | ||||
|  | ||||
|     // Represents a call nested within a `batchFill`. | ||||
|     struct WrappedBatchCall { | ||||
|         // The selector of the function to call. | ||||
|         bytes4 selector; | ||||
|         // Amount of `inputToken` to sell. | ||||
|         uint256 sellAmount; | ||||
|         // ABI-encoded parameters needed to perform the call. | ||||
|         bytes data; | ||||
|     } | ||||
|  | ||||
|     // Parameters for `multiHopFill`. | ||||
|     struct MultiHopFillData { | ||||
|         // The sell path, i.e. | ||||
|         // tokens = [inputToken, hopToken1, ..., hopTokenN, outputToken] | ||||
|         address[] tokens; | ||||
|         // The amount of `tokens[0]` to sell. | ||||
|         uint256 sellAmount; | ||||
|         // The nested calls to perform. | ||||
|         WrappedMultiHopCall[] calls; | ||||
|     } | ||||
|  | ||||
|     // Represents a call nested within a `multiHopFill`. | ||||
|     struct WrappedMultiHopCall { | ||||
|         // The selector of the function to call. | ||||
|         bytes4 selector; | ||||
|         // ABI-encoded parameters needed to perform the call. | ||||
|         bytes data; | ||||
|     } | ||||
|  | ||||
|     event LiquidityProviderSwap( | ||||
|         address inputToken, | ||||
|         address outputToken, | ||||
|         uint256 inputTokenAmount, | ||||
|         uint256 outputTokenAmount, | ||||
|         address provider, | ||||
|         address recipient | ||||
|     ); | ||||
|  | ||||
|     event ExpiredRfqOrder( | ||||
|         bytes32 orderHash, | ||||
|         address maker, | ||||
|         uint64 expiry | ||||
|     ); | ||||
|  | ||||
|     /// @dev Executes a batch of fills selling `fillData.inputToken` | ||||
|     ///      for `fillData.outputToken` in sequence. Refer to the | ||||
|     ///      internal variant `_batchFill` for the allowed nested | ||||
|     ///      operations. | ||||
|     /// @param fillData Encodes the input/output tokens, the sell | ||||
|     ///        amount, and the nested operations for this batch fill. | ||||
|     /// @param minBuyAmount The minimum amount of `fillData.outputToken` | ||||
|     ///        to buy. Reverts if this amount is not met. | ||||
|     /// @return outputTokenAmount The amount of the output token bought. | ||||
|     function batchFill( | ||||
|         BatchFillData calldata fillData, | ||||
|         uint256 minBuyAmount | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns (uint256 outputTokenAmount); | ||||
|  | ||||
|     /// @dev Executes a sequence of fills "hopping" through the | ||||
|     ///      path of tokens given by `fillData.tokens`. Refer to the | ||||
|     ///      internal variant `_multiHopFill` for the allowed nested | ||||
|     ///      operations. | ||||
|     /// @param fillData Encodes the path of tokens, the sell amount, | ||||
|     ///        and the nested operations for this multi-hop fill. | ||||
|     /// @param minBuyAmount The minimum amount of the output token | ||||
|     ///        to buy. Reverts if this amount is not met. | ||||
|     /// @return outputTokenAmount The amount of the output token bought. | ||||
|     function multiHopFill( | ||||
|         MultiHopFillData calldata fillData, | ||||
|         uint256 minBuyAmount | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns (uint256 outputTokenAmount); | ||||
| } | ||||
| @@ -0,0 +1,116 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| import "../libs/LibNativeOrder.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Events emitted by NativeOrdersFeature. | ||||
| interface INativeOrdersEvents { | ||||
|  | ||||
|     /// @dev Emitted whenever a `LimitOrder` is filled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param taker The taker of the order. | ||||
|     /// @param feeRecipient Fee recipient of the order. | ||||
|     /// @param takerTokenFilledAmount How much taker token was filled. | ||||
|     /// @param makerTokenFilledAmount How much maker token was filled. | ||||
|     /// @param protocolFeePaid How much protocol fee was paid. | ||||
|     /// @param pool The fee pool associated with this order. | ||||
|     event LimitOrderFilled( | ||||
|         bytes32 orderHash, | ||||
|         address maker, | ||||
|         address taker, | ||||
|         address feeRecipient, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint128 takerTokenFilledAmount, | ||||
|         uint128 makerTokenFilledAmount, | ||||
|         uint128 takerTokenFeeFilledAmount, | ||||
|         uint256 protocolFeePaid, | ||||
|         bytes32 pool | ||||
|     ); | ||||
|  | ||||
|     /// @dev Emitted whenever an `RfqOrder` is filled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param taker The taker of the order. | ||||
|     /// @param takerTokenFilledAmount How much taker token was filled. | ||||
|     /// @param makerTokenFilledAmount How much maker token was filled. | ||||
|     /// @param pool The fee pool associated with this order. | ||||
|     event RfqOrderFilled( | ||||
|         bytes32 orderHash, | ||||
|         address maker, | ||||
|         address taker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint128 takerTokenFilledAmount, | ||||
|         uint128 makerTokenFilledAmount, | ||||
|         bytes32 pool | ||||
|     ); | ||||
|  | ||||
|     /// @dev Emitted whenever a limit or RFQ order is cancelled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The order maker. | ||||
|     event OrderCancelled( | ||||
|         bytes32 orderHash, | ||||
|         address maker | ||||
|     ); | ||||
|  | ||||
|     /// @dev Emitted whenever Limit orders are cancelled by pair by a maker. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param makerToken The maker token in a pair for the orders cancelled. | ||||
|     /// @param takerToken The taker token in a pair for the orders cancelled. | ||||
|     /// @param minValidSalt The new minimum valid salt an order with this pair must | ||||
|     ///        have. | ||||
|     event PairCancelledLimitOrders( | ||||
|         address maker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ); | ||||
|  | ||||
|     /// @dev Emitted whenever RFQ orders are cancelled by pair by a maker. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param makerToken The maker token in a pair for the orders cancelled. | ||||
|     /// @param takerToken The taker token in a pair for the orders cancelled. | ||||
|     /// @param minValidSalt The new minimum valid salt an order with this pair must | ||||
|     ///        have. | ||||
|     event PairCancelledRfqOrders( | ||||
|         address maker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ); | ||||
|  | ||||
|     /// @dev Emitted when new addresses are allowed or disallowed to fill | ||||
|     ///      orders with a given txOrigin. | ||||
|     /// @param origin The address doing the allowing. | ||||
|     /// @param addrs The address being allowed/disallowed. | ||||
|     /// @param allowed Indicates whether the address should be allowed. | ||||
|     event RfqOrderOriginsAllowed( | ||||
|         address origin, | ||||
|         address[] addrs, | ||||
|         bool allowed | ||||
|     ); | ||||
| } | ||||
| @@ -21,98 +21,15 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "./libs/LibSignature.sol"; | ||||
| import "./libs/LibNativeOrder.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| import "../libs/LibNativeOrder.sol"; | ||||
| import "./INativeOrdersEvents.sol"; | ||||
| 
 | ||||
| 
 | ||||
| /// @dev Feature for interacting with limit orders. | ||||
| interface INativeOrdersFeature { | ||||
| 
 | ||||
|     /// @dev Emitted whenever a `LimitOrder` is filled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param taker The taker of the order. | ||||
|     /// @param feeRecipient Fee recipient of the order. | ||||
|     /// @param takerTokenFilledAmount How much taker token was filled. | ||||
|     /// @param makerTokenFilledAmount How much maker token was filled. | ||||
|     /// @param protocolFeePaid How much protocol fee was paid. | ||||
|     /// @param pool The fee pool associated with this order. | ||||
|     event LimitOrderFilled( | ||||
|         bytes32 orderHash, | ||||
|         address maker, | ||||
|         address taker, | ||||
|         address feeRecipient, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint128 takerTokenFilledAmount, | ||||
|         uint128 makerTokenFilledAmount, | ||||
|         uint128 takerTokenFeeFilledAmount, | ||||
|         uint256 protocolFeePaid, | ||||
|         bytes32 pool | ||||
|     ); | ||||
| 
 | ||||
|     /// @dev Emitted whenever an `RfqOrder` is filled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param taker The taker of the order. | ||||
|     /// @param takerTokenFilledAmount How much taker token was filled. | ||||
|     /// @param makerTokenFilledAmount How much maker token was filled. | ||||
|     /// @param pool The fee pool associated with this order. | ||||
|     event RfqOrderFilled( | ||||
|         bytes32 orderHash, | ||||
|         address maker, | ||||
|         address taker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint128 takerTokenFilledAmount, | ||||
|         uint128 makerTokenFilledAmount, | ||||
|         bytes32 pool | ||||
|     ); | ||||
| 
 | ||||
|     /// @dev Emitted whenever a limit or RFQ order is cancelled. | ||||
|     /// @param orderHash The canonical hash of the order. | ||||
|     /// @param maker The order maker. | ||||
|     event OrderCancelled( | ||||
|         bytes32 orderHash, | ||||
|         address maker | ||||
|     ); | ||||
| 
 | ||||
|     /// @dev Emitted whenever Limit orders are cancelled by pair by a maker. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param makerToken The maker token in a pair for the orders cancelled. | ||||
|     /// @param takerToken The taker token in a pair for the orders cancelled. | ||||
|     /// @param minValidSalt The new minimum valid salt an order with this pair must | ||||
|     ///        have. | ||||
|     event PairCancelledLimitOrders( | ||||
|         address maker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ); | ||||
| 
 | ||||
|     /// @dev Emitted whenever RFQ orders are cancelled by pair by a maker. | ||||
|     /// @param maker The maker of the order. | ||||
|     /// @param makerToken The maker token in a pair for the orders cancelled. | ||||
|     /// @param takerToken The taker token in a pair for the orders cancelled. | ||||
|     /// @param minValidSalt The new minimum valid salt an order with this pair must | ||||
|     ///        have. | ||||
|     event PairCancelledRfqOrders( | ||||
|         address maker, | ||||
|         address makerToken, | ||||
|         address takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ); | ||||
| 
 | ||||
|     /// @dev Emitted when new addresses are allowed or disallowed to fill | ||||
|     ///      orders with a given txOrigin. | ||||
|     /// @param origin The address doing the allowing. | ||||
|     /// @param addrs The address being allowed/disallowed. | ||||
|     /// @param allowed Indicates whether the address should be allowed. | ||||
|     event RfqOrderOriginsAllowed( | ||||
|         address origin, | ||||
|         address[] addrs, | ||||
|         bool allowed | ||||
|     ); | ||||
| interface INativeOrdersFeature is | ||||
|     INativeOrdersEvents | ||||
| { | ||||
| 
 | ||||
|     /// @dev Transfers protocol fees from the `FeeCollector` pools into | ||||
|     ///      the staking contract. | ||||
| @@ -21,8 +21,8 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "../transformers/IERC20Transformer.sol"; | ||||
| import "../external/IFlashWallet.sol"; | ||||
| import "../../transformers/IERC20Transformer.sol"; | ||||
| import "../../external/IFlashWallet.sol"; | ||||
| 
 | ||||
| 
 | ||||
| /// @dev Feature to composably transform between ERC20 tokens. | ||||
| @@ -21,10 +21,15 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "../../errors/LibNativeOrdersRichErrors.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev A library for common native order operations. | ||||
| library LibNativeOrder { | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     enum OrderStatus { | ||||
|         INVALID, | ||||
| @@ -216,4 +221,23 @@ library LibNativeOrder { | ||||
|             structHash := keccak256(mem, 0x160) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Refund any leftover protocol fees in `msg.value` to `msg.sender`. | ||||
|     /// @param ethProtocolFeePaid How much ETH was paid in protocol fees. | ||||
|     function refundExcessProtocolFeeToSender(uint256 ethProtocolFeePaid) | ||||
|         internal | ||||
|     { | ||||
|         if (msg.value > ethProtocolFeePaid && msg.sender != address(this)) { | ||||
|             uint256 refundAmount = msg.value.safeSub(ethProtocolFeePaid); | ||||
|             (bool success,) = msg | ||||
|                 .sender | ||||
|                 .call{value: refundAmount}(""); | ||||
|             if (!success) { | ||||
|                 LibNativeOrdersRichErrors.ProtocolFeeRefundFailed( | ||||
|                     msg.sender, | ||||
|                     refundAmount | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,266 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "../../errors/LibNativeOrdersRichErrors.sol"; | ||||
| import "../../storage/LibNativeOrdersStorage.sol"; | ||||
| import "../interfaces/INativeOrdersEvents.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| import "../libs/LibNativeOrder.sol"; | ||||
| import "./NativeOrdersInfo.sol"; | ||||
|  | ||||
| /// @dev Feature for cancelling limit and RFQ orders. | ||||
| abstract contract NativeOrdersCancellation is | ||||
|     INativeOrdersEvents, | ||||
|     NativeOrdersInfo | ||||
| { | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     /// @dev Highest bit of a uint256, used to flag cancelled orders. | ||||
|     uint256 private constant HIGH_BIT = 1 << 255; | ||||
|  | ||||
|     constructor( | ||||
|         address zeroExAddress, | ||||
|         bytes32 greedyTokensBloomFilter | ||||
|     ) | ||||
|         internal | ||||
|         NativeOrdersInfo(zeroExAddress, greedyTokensBloomFilter) | ||||
|     { | ||||
|         // solhint-disable no-empty-blocks | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel a single limit order. The caller must be the maker. | ||||
|     ///      Silently succeeds if the order has already been cancelled. | ||||
|     /// @param order The limit order. | ||||
|     function cancelLimitOrder(LibNativeOrder.LimitOrder memory order) | ||||
|         public | ||||
|     { | ||||
|         bytes32 orderHash = getLimitOrderHash(order); | ||||
|         if (msg.sender != order.maker) { | ||||
|             LibNativeOrdersRichErrors.OnlyOrderMakerAllowed( | ||||
|                 orderHash, | ||||
|                 msg.sender, | ||||
|                 order.maker | ||||
|             ).rrevert(); | ||||
|         } | ||||
|         _cancelOrderHash(orderHash, order.maker); | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel a single RFQ order. The caller must be the maker. | ||||
|     ///      Silently succeeds if the order has already been cancelled. | ||||
|     /// @param order The RFQ order. | ||||
|     function cancelRfqOrder(LibNativeOrder.RfqOrder memory order) | ||||
|         public | ||||
|     { | ||||
|         bytes32 orderHash = getRfqOrderHash(order); | ||||
|         if (msg.sender != order.maker) { | ||||
|             LibNativeOrdersRichErrors.OnlyOrderMakerAllowed( | ||||
|                 orderHash, | ||||
|                 msg.sender, | ||||
|                 order.maker | ||||
|             ).rrevert(); | ||||
|         } | ||||
|         _cancelOrderHash(orderHash, order.maker); | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel multiple limit orders. The caller must be the maker. | ||||
|     ///      Silently succeeds if the order has already been cancelled. | ||||
|     /// @param orders The limit orders. | ||||
|     function batchCancelLimitOrders(LibNativeOrder.LimitOrder[] memory orders) | ||||
|         public | ||||
|     { | ||||
|         for (uint256 i = 0; i < orders.length; ++i) { | ||||
|             cancelLimitOrder(orders[i]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel multiple RFQ orders. The caller must be the maker. | ||||
|     ///      Silently succeeds if the order has already been cancelled. | ||||
|     /// @param orders The RFQ orders. | ||||
|     function batchCancelRfqOrders(LibNativeOrder.RfqOrder[] memory orders) | ||||
|         public | ||||
|     { | ||||
|         for (uint256 i = 0; i < orders.length; ++i) { | ||||
|             cancelRfqOrder(orders[i]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel all limit orders for a given maker and pair with a salt less | ||||
|     ///      than the value provided. The caller must be the maker. Subsequent | ||||
|     ///      calls to this function with the same caller and pair require the | ||||
|     ///      new salt to be >= the old salt. | ||||
|     /// @param makerToken The maker token. | ||||
|     /// @param takerToken The taker token. | ||||
|     /// @param minValidSalt The new minimum valid salt. | ||||
|     function cancelPairLimitOrders( | ||||
|         IERC20TokenV06 makerToken, | ||||
|         IERC20TokenV06 takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         LibNativeOrdersStorage.Storage storage stor = | ||||
|             LibNativeOrdersStorage.getStorage(); | ||||
|  | ||||
|         uint256 oldMinValidSalt = | ||||
|             stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|                 [msg.sender] | ||||
|                 [address(makerToken)] | ||||
|                 [address(takerToken)]; | ||||
|  | ||||
|         // New min salt must >= the old one. | ||||
|         if (oldMinValidSalt > minValidSalt) { | ||||
|             LibNativeOrdersRichErrors. | ||||
|                 CancelSaltTooLowError(minValidSalt, oldMinValidSalt) | ||||
|                     .rrevert(); | ||||
|         } | ||||
|  | ||||
|         stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|             [msg.sender] | ||||
|             [address(makerToken)] | ||||
|             [address(takerToken)] = minValidSalt; | ||||
|  | ||||
|         emit PairCancelledLimitOrders( | ||||
|             msg.sender, | ||||
|             address(makerToken), | ||||
|             address(takerToken), | ||||
|             minValidSalt | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel all limit orders for a given maker and pair with a salt less | ||||
|     ///      than the value provided. The caller must be the maker. Subsequent | ||||
|     ///      calls to this function with the same caller and pair require the | ||||
|     ///      new salt to be >= the old salt. | ||||
|     /// @param makerTokens The maker tokens. | ||||
|     /// @param takerTokens The taker tokens. | ||||
|     /// @param minValidSalts The new minimum valid salts. | ||||
|     function batchCancelPairLimitOrders( | ||||
|         IERC20TokenV06[] memory makerTokens, | ||||
|         IERC20TokenV06[] memory takerTokens, | ||||
|         uint256[] memory minValidSalts | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         require( | ||||
|             makerTokens.length == takerTokens.length && | ||||
|             makerTokens.length == minValidSalts.length, | ||||
|             "NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS" | ||||
|         ); | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokens.length; ++i) { | ||||
|             cancelPairLimitOrders( | ||||
|                 makerTokens[i], | ||||
|                 takerTokens[i], | ||||
|                 minValidSalts[i] | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel all RFQ orders for a given maker and pair with a salt less | ||||
|     ///      than the value provided. The caller must be the maker. Subsequent | ||||
|     ///      calls to this function with the same caller and pair require the | ||||
|     ///      new salt to be >= the old salt. | ||||
|     /// @param makerToken The maker token. | ||||
|     /// @param takerToken The taker token. | ||||
|     /// @param minValidSalt The new minimum valid salt. | ||||
|     function cancelPairRfqOrders( | ||||
|         IERC20TokenV06 makerToken, | ||||
|         IERC20TokenV06 takerToken, | ||||
|         uint256 minValidSalt | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         LibNativeOrdersStorage.Storage storage stor = | ||||
|             LibNativeOrdersStorage.getStorage(); | ||||
|  | ||||
|         uint256 oldMinValidSalt = | ||||
|             stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|                 [msg.sender] | ||||
|                 [address(makerToken)] | ||||
|                 [address(takerToken)]; | ||||
|  | ||||
|         // New min salt must >= the old one. | ||||
|         if (oldMinValidSalt > minValidSalt) { | ||||
|             LibNativeOrdersRichErrors. | ||||
|                 CancelSaltTooLowError(minValidSalt, oldMinValidSalt) | ||||
|                     .rrevert(); | ||||
|         } | ||||
|  | ||||
|         stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|             [msg.sender] | ||||
|             [address(makerToken)] | ||||
|             [address(takerToken)] = minValidSalt; | ||||
|  | ||||
|         emit PairCancelledRfqOrders( | ||||
|             msg.sender, | ||||
|             address(makerToken), | ||||
|             address(takerToken), | ||||
|             minValidSalt | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel all RFQ orders for a given maker and pair with a salt less | ||||
|     ///      than the value provided. The caller must be the maker. Subsequent | ||||
|     ///      calls to this function with the same caller and pair require the | ||||
|     ///      new salt to be >= the old salt. | ||||
|     /// @param makerTokens The maker tokens. | ||||
|     /// @param takerTokens The taker tokens. | ||||
|     /// @param minValidSalts The new minimum valid salts. | ||||
|     function batchCancelPairRfqOrders( | ||||
|         IERC20TokenV06[] memory makerTokens, | ||||
|         IERC20TokenV06[] memory takerTokens, | ||||
|         uint256[] memory minValidSalts | ||||
|     ) | ||||
|         public | ||||
|     { | ||||
|         require( | ||||
|             makerTokens.length == takerTokens.length && | ||||
|             makerTokens.length == minValidSalts.length, | ||||
|             "NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS" | ||||
|         ); | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokens.length; ++i) { | ||||
|             cancelPairRfqOrders( | ||||
|                 makerTokens[i], | ||||
|                 takerTokens[i], | ||||
|                 minValidSalts[i] | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Cancel a limit or RFQ order directly by its order hash. | ||||
|     /// @param orderHash The order's order hash. | ||||
|     /// @param maker The order's maker. | ||||
|     function _cancelOrderHash(bytes32 orderHash, address maker) | ||||
|         private | ||||
|     { | ||||
|         LibNativeOrdersStorage.Storage storage stor = | ||||
|             LibNativeOrdersStorage.getStorage(); | ||||
|         // Set the high bit on the raw taker token fill amount to indicate | ||||
|         // a cancel. It's OK to cancel twice. | ||||
|         stor.orderHashToTakerTokenFilledAmount[orderHash] |= HIGH_BIT; | ||||
|  | ||||
|         emit OrderCancelled(orderHash, maker); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,394 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "../../fixins/FixinEIP712.sol"; | ||||
| import "../../fixins/FixinTokenSpender.sol"; | ||||
| import "../../storage/LibNativeOrdersStorage.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| import "../libs/LibNativeOrder.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Feature for getting info about limit and RFQ orders. | ||||
| abstract contract NativeOrdersInfo is | ||||
|     FixinEIP712, | ||||
|     FixinTokenSpender | ||||
| { | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     // @dev Params for `_getActualFillableTakerTokenAmount()`. | ||||
|     struct GetActualFillableTakerTokenAmountParams { | ||||
|         address maker; | ||||
|         IERC20TokenV06 makerToken; | ||||
|         uint128 orderMakerAmount; | ||||
|         uint128 orderTakerAmount; | ||||
|         LibNativeOrder.OrderInfo orderInfo; | ||||
|     } | ||||
|  | ||||
|     /// @dev Highest bit of a uint256, used to flag cancelled orders. | ||||
|     uint256 private constant HIGH_BIT = 1 << 255; | ||||
|  | ||||
|     constructor( | ||||
|         address zeroExAddress, | ||||
|         bytes32 greedyTokensBloomFilter | ||||
|     ) | ||||
|         internal | ||||
|         FixinEIP712(zeroExAddress) | ||||
|         FixinTokenSpender(greedyTokensBloomFilter) | ||||
|     { | ||||
|         // solhint-disable no-empty-blocks | ||||
|     } | ||||
|  | ||||
|     /// @dev Get the order info for a limit order. | ||||
|     /// @param order The limit order. | ||||
|     /// @return orderInfo Info about the order. | ||||
|     function getLimitOrderInfo(LibNativeOrder.LimitOrder memory order) | ||||
|         public | ||||
|         view | ||||
|         returns (LibNativeOrder.OrderInfo memory orderInfo) | ||||
|     { | ||||
|         // Recover maker and compute order hash. | ||||
|         orderInfo.orderHash = getLimitOrderHash(order); | ||||
|         uint256 minValidSalt = LibNativeOrdersStorage.getStorage() | ||||
|             .limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|                 [order.maker] | ||||
|                 [address(order.makerToken)] | ||||
|                 [address(order.takerToken)]; | ||||
|         _populateCommonOrderInfoFields( | ||||
|             orderInfo, | ||||
|             order.takerAmount, | ||||
|             order.expiry, | ||||
|             order.salt, | ||||
|             minValidSalt | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Get the order info for an RFQ order. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @return orderInfo Info about the order. | ||||
|     function getRfqOrderInfo(LibNativeOrder.RfqOrder memory order) | ||||
|         public | ||||
|         view | ||||
|         returns (LibNativeOrder.OrderInfo memory orderInfo) | ||||
|     { | ||||
|         // Recover maker and compute order hash. | ||||
|         orderInfo.orderHash = getRfqOrderHash(order); | ||||
|         uint256 minValidSalt = LibNativeOrdersStorage.getStorage() | ||||
|             .rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt | ||||
|                 [order.maker] | ||||
|                 [address(order.makerToken)] | ||||
|                 [address(order.takerToken)]; | ||||
|         _populateCommonOrderInfoFields( | ||||
|             orderInfo, | ||||
|             order.takerAmount, | ||||
|             order.expiry, | ||||
|             order.salt, | ||||
|             minValidSalt | ||||
|         ); | ||||
|  | ||||
|         // Check for missing txOrigin. | ||||
|         if (order.txOrigin == address(0)) { | ||||
|             orderInfo.status = LibNativeOrder.OrderStatus.INVALID; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Get the canonical hash of a limit order. | ||||
|     /// @param order The limit order. | ||||
|     /// @return orderHash The order hash. | ||||
|     function getLimitOrderHash(LibNativeOrder.LimitOrder memory order) | ||||
|         public | ||||
|         view | ||||
|         returns (bytes32 orderHash) | ||||
|     { | ||||
|         return _getEIP712Hash( | ||||
|             LibNativeOrder.getLimitOrderStructHash(order) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Get the canonical hash of an RFQ order. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @return orderHash The order hash. | ||||
|     function getRfqOrderHash(LibNativeOrder.RfqOrder memory order) | ||||
|         public | ||||
|         view | ||||
|         returns (bytes32 orderHash) | ||||
|     { | ||||
|         return _getEIP712Hash( | ||||
|             LibNativeOrder.getRfqOrderStructHash(order) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Get order info, fillable amount, and signature validity for a limit order. | ||||
|     ///      Fillable amount is determined using balances and allowances of the maker. | ||||
|     /// @param order The limit order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @return orderInfo Info about the order. | ||||
|     /// @return actualFillableTakerTokenAmount How much of the order is fillable | ||||
|     ///         based on maker funds, in taker tokens. | ||||
|     /// @return isSignatureValid Whether the signature is valid. | ||||
|     function getLimitOrderRelevantState( | ||||
|         LibNativeOrder.LimitOrder memory order, | ||||
|         LibSignature.Signature calldata signature | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibNativeOrder.OrderInfo memory orderInfo, | ||||
|             uint128 actualFillableTakerTokenAmount, | ||||
|             bool isSignatureValid | ||||
|         ) | ||||
|     { | ||||
|         orderInfo = getLimitOrderInfo(order); | ||||
|         actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount( | ||||
|             GetActualFillableTakerTokenAmountParams({ | ||||
|                 maker: order.maker, | ||||
|                 makerToken: order.makerToken, | ||||
|                 orderMakerAmount: order.makerAmount, | ||||
|                 orderTakerAmount: order.takerAmount, | ||||
|                 orderInfo: orderInfo | ||||
|             }) | ||||
|         ); | ||||
|         isSignatureValid = order.maker == | ||||
|             LibSignature.getSignerOfHash(orderInfo.orderHash, signature); | ||||
|     } | ||||
|  | ||||
|     /// @dev Get order info, fillable amount, and signature validity for an RFQ order. | ||||
|     ///      Fillable amount is determined using balances and allowances of the maker. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @return orderInfo Info about the order. | ||||
|     /// @return actualFillableTakerTokenAmount How much of the order is fillable | ||||
|     ///         based on maker funds, in taker tokens. | ||||
|     /// @return isSignatureValid Whether the signature is valid. | ||||
|     function getRfqOrderRelevantState( | ||||
|         LibNativeOrder.RfqOrder memory order, | ||||
|         LibSignature.Signature memory signature | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns ( | ||||
|             LibNativeOrder.OrderInfo memory orderInfo, | ||||
|             uint128 actualFillableTakerTokenAmount, | ||||
|             bool isSignatureValid | ||||
|         ) | ||||
|     { | ||||
|         orderInfo = getRfqOrderInfo(order); | ||||
|         actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount( | ||||
|             GetActualFillableTakerTokenAmountParams({ | ||||
|                 maker: order.maker, | ||||
|                 makerToken: order.makerToken, | ||||
|                 orderMakerAmount: order.makerAmount, | ||||
|                 orderTakerAmount: order.takerAmount, | ||||
|                 orderInfo: orderInfo | ||||
|             }) | ||||
|         ); | ||||
|         isSignatureValid = order.maker == | ||||
|             LibSignature.getSignerOfHash(orderInfo.orderHash, signature); | ||||
|     } | ||||
|  | ||||
|     /// @dev Batch version of `getLimitOrderRelevantState()`, without reverting. | ||||
|     ///      Orders that would normally cause `getLimitOrderRelevantState()` | ||||
|     ///      to revert will have empty results. | ||||
|     /// @param orders The limit orders. | ||||
|     /// @param signatures The order signatures. | ||||
|     /// @return orderInfos Info about the orders. | ||||
|     /// @return actualFillableTakerTokenAmounts How much of each order is fillable | ||||
|     ///         based on maker funds, in taker tokens. | ||||
|     /// @return isSignatureValids Whether each signature is valid for the order. | ||||
|     function batchGetLimitOrderRelevantStates( | ||||
|         LibNativeOrder.LimitOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             LibNativeOrder.OrderInfo[] memory orderInfos, | ||||
|             uint128[] memory actualFillableTakerTokenAmounts, | ||||
|             bool[] memory isSignatureValids | ||||
|         ) | ||||
|     { | ||||
|         require( | ||||
|             orders.length == signatures.length, | ||||
|             "NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS" | ||||
|         ); | ||||
|         orderInfos = new LibNativeOrder.OrderInfo[](orders.length); | ||||
|         actualFillableTakerTokenAmounts = new uint128[](orders.length); | ||||
|         isSignatureValids = new bool[](orders.length); | ||||
|         for (uint256 i = 0; i < orders.length; ++i) { | ||||
|             try | ||||
|                 this.getLimitOrderRelevantState(orders[i], signatures[i]) | ||||
|                     returns ( | ||||
|                         LibNativeOrder.OrderInfo memory orderInfo, | ||||
|                         uint128 actualFillableTakerTokenAmount, | ||||
|                         bool isSignatureValid | ||||
|                     ) | ||||
|             { | ||||
|                 orderInfos[i] = orderInfo; | ||||
|                 actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount; | ||||
|                 isSignatureValids[i] = isSignatureValid; | ||||
|             } | ||||
|             catch {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Batch version of `getRfqOrderRelevantState()`, without reverting. | ||||
|     ///      Orders that would normally cause `getRfqOrderRelevantState()` | ||||
|     ///      to revert will have empty results. | ||||
|     /// @param orders The RFQ orders. | ||||
|     /// @param signatures The order signatures. | ||||
|     /// @return orderInfos Info about the orders. | ||||
|     /// @return actualFillableTakerTokenAmounts How much of each order is fillable | ||||
|     ///         based on maker funds, in taker tokens. | ||||
|     /// @return isSignatureValids Whether each signature is valid for the order. | ||||
|     function batchGetRfqOrderRelevantStates( | ||||
|         LibNativeOrder.RfqOrder[] calldata orders, | ||||
|         LibSignature.Signature[] calldata signatures | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             LibNativeOrder.OrderInfo[] memory orderInfos, | ||||
|             uint128[] memory actualFillableTakerTokenAmounts, | ||||
|             bool[] memory isSignatureValids | ||||
|         ) | ||||
|     { | ||||
|         require( | ||||
|             orders.length == signatures.length, | ||||
|             "NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS" | ||||
|         ); | ||||
|         orderInfos = new LibNativeOrder.OrderInfo[](orders.length); | ||||
|         actualFillableTakerTokenAmounts = new uint128[](orders.length); | ||||
|         isSignatureValids = new bool[](orders.length); | ||||
|         for (uint256 i = 0; i < orders.length; ++i) { | ||||
|             try | ||||
|                 this.getRfqOrderRelevantState(orders[i], signatures[i]) | ||||
|                     returns ( | ||||
|                         LibNativeOrder.OrderInfo memory orderInfo, | ||||
|                         uint128 actualFillableTakerTokenAmount, | ||||
|                         bool isSignatureValid | ||||
|                     ) | ||||
|             { | ||||
|                 orderInfos[i] = orderInfo; | ||||
|                 actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount; | ||||
|                 isSignatureValids[i] = isSignatureValid; | ||||
|             } | ||||
|             catch {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Populate `status` and `takerTokenFilledAmount` fields in | ||||
|     ///      `orderInfo`, which use the same code path for both limit and | ||||
|     ///      RFQ orders. | ||||
|     /// @param orderInfo `OrderInfo` with `orderHash` and `maker` filled. | ||||
|     /// @param takerAmount The order's taker token amount.. | ||||
|     /// @param expiry The order's expiry. | ||||
|     /// @param salt The order's salt. | ||||
|     /// @param salt The minimum valid salt for the maker and pair combination. | ||||
|     function _populateCommonOrderInfoFields( | ||||
|         LibNativeOrder.OrderInfo memory orderInfo, | ||||
|         uint128 takerAmount, | ||||
|         uint64 expiry, | ||||
|         uint256 salt, | ||||
|         uint256 minValidSalt | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|     { | ||||
|         LibNativeOrdersStorage.Storage storage stor = | ||||
|             LibNativeOrdersStorage.getStorage(); | ||||
|  | ||||
|         // Get the filled and direct cancel state. | ||||
|         { | ||||
|             // The high bit of the raw taker token filled amount will be set | ||||
|             // if the order was cancelled. | ||||
|             uint256 rawTakerTokenFilledAmount = | ||||
|                 stor.orderHashToTakerTokenFilledAmount[orderInfo.orderHash]; | ||||
|             orderInfo.takerTokenFilledAmount = uint128(rawTakerTokenFilledAmount); | ||||
|             if (orderInfo.takerTokenFilledAmount >= takerAmount) { | ||||
|                 orderInfo.status = LibNativeOrder.OrderStatus.FILLED; | ||||
|                 return; | ||||
|             } | ||||
|             if (rawTakerTokenFilledAmount & HIGH_BIT != 0) { | ||||
|                 orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED; | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Check for expiration. | ||||
|         if (expiry <= uint64(block.timestamp)) { | ||||
|             orderInfo.status = LibNativeOrder.OrderStatus.EXPIRED; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Check if the order was cancelled by salt. | ||||
|         if (minValidSalt > salt) { | ||||
|             orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED; | ||||
|             return; | ||||
|         } | ||||
|         orderInfo.status = LibNativeOrder.OrderStatus.FILLABLE; | ||||
|     } | ||||
|  | ||||
|     /// @dev Calculate the actual fillable taker token amount of an order | ||||
|     ///      based on maker allowance and balances. | ||||
|     function _getActualFillableTakerTokenAmount( | ||||
|         GetActualFillableTakerTokenAmountParams memory params | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint128 actualFillableTakerTokenAmount) | ||||
|     { | ||||
|         if (params.orderMakerAmount == 0 || params.orderTakerAmount == 0) { | ||||
|             // Empty order. | ||||
|             return 0; | ||||
|         } | ||||
|         if (params.orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) { | ||||
|             // Not fillable. | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         // Get the fillable maker amount based on the order quantities and | ||||
|         // previously filled amount | ||||
|         uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor( | ||||
|             uint256( | ||||
|                 params.orderTakerAmount | ||||
|                 - params.orderInfo.takerTokenFilledAmount | ||||
|             ), | ||||
|             uint256(params.orderTakerAmount), | ||||
|             uint256(params.orderMakerAmount) | ||||
|         ); | ||||
|         // Clamp it to the amount of maker tokens we can spend on behalf of the | ||||
|         // maker. | ||||
|         fillableMakerTokenAmount = LibSafeMathV06.min256( | ||||
|             fillableMakerTokenAmount, | ||||
|             _getSpendableERC20BalanceOf(params.makerToken, params.maker) | ||||
|         ); | ||||
|         // Convert to taker token amount. | ||||
|         return LibMathV06.getPartialAmountCeil( | ||||
|             fillableMakerTokenAmount, | ||||
|             uint256(params.orderMakerAmount), | ||||
|             uint256(params.orderTakerAmount) | ||||
|         ).safeDowncastToUint128(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "../../fixins/FixinProtocolFees.sol"; | ||||
| import "../../errors/LibNativeOrdersRichErrors.sol"; | ||||
| import "../../vendor/v3/IStaking.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Mixin for protocol fee utility functions. | ||||
| abstract contract NativeOrdersProtocolFees is | ||||
|     FixinProtocolFees | ||||
| { | ||||
|     using LibSafeMathV06 for uint256; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     constructor( | ||||
|         IEtherTokenV06 weth, | ||||
|         IStaking staking, | ||||
|         FeeCollectorController feeCollectorController, | ||||
|         uint32 protocolFeeMultiplier | ||||
|     ) | ||||
|         internal | ||||
|         FixinProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier) | ||||
|     { | ||||
|         // solhint-disable no-empty-blocks | ||||
|     } | ||||
|  | ||||
|     /// @dev Transfers protocol fees from the `FeeCollector` pools into | ||||
|     ///      the staking contract. | ||||
|     /// @param poolIds Staking pool IDs | ||||
|     function transferProtocolFeesForPools(bytes32[] calldata poolIds) | ||||
|         external | ||||
|     { | ||||
|         for (uint256 i = 0; i < poolIds.length; ++i) { | ||||
|             _transferFeesForPool(poolIds[i]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Get the protocol fee multiplier. This should be multiplied by the | ||||
|     ///      gas price to arrive at the required protocol fee to fill a native order. | ||||
|     /// @return multiplier The protocol fee multiplier. | ||||
|     function getProtocolFeeMultiplier() | ||||
|         external | ||||
|         view | ||||
|         returns (uint32 multiplier) | ||||
|     { | ||||
|         return PROTOCOL_FEE_MULTIPLIER; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,569 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
| import "../../errors/LibNativeOrdersRichErrors.sol"; | ||||
| import "../../fixins/FixinCommon.sol"; | ||||
| import "../../storage/LibNativeOrdersStorage.sol"; | ||||
| import "../../vendor/v3/IStaking.sol"; | ||||
| import "../interfaces/INativeOrdersEvents.sol"; | ||||
| import "../libs/LibSignature.sol"; | ||||
| import "../libs/LibNativeOrder.sol"; | ||||
| import "./NativeOrdersCancellation.sol"; | ||||
| import "./NativeOrdersProtocolFees.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Mixin for settling limit and RFQ orders. | ||||
| abstract contract NativeOrdersSettlement is | ||||
|     INativeOrdersEvents, | ||||
|     NativeOrdersCancellation, | ||||
|     NativeOrdersProtocolFees, | ||||
|     FixinCommon | ||||
| { | ||||
|     using LibSafeMathV06 for uint128; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     /// @dev Params for `_settleOrder()`. | ||||
|     struct SettleOrderInfo { | ||||
|         // Order hash. | ||||
|         bytes32 orderHash; | ||||
|         // Maker of the order. | ||||
|         address maker; | ||||
|         // Taker of the order. | ||||
|         address taker; | ||||
|         // Maker token. | ||||
|         IERC20TokenV06 makerToken; | ||||
|         // Taker token. | ||||
|         IERC20TokenV06 takerToken; | ||||
|         // Maker token amount. | ||||
|         uint128 makerAmount; | ||||
|         // Taker token amount. | ||||
|         uint128 takerAmount; | ||||
|         // Maximum taker token amount to fill. | ||||
|         uint128 takerTokenFillAmount; | ||||
|         // How much taker token amount has already been filled in this order. | ||||
|         uint128 takerTokenFilledAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev Params for `_fillLimitOrderPrivate()` | ||||
|     struct FillLimitOrderPrivateParams { | ||||
|         // The limit order. | ||||
|         LibNativeOrder.LimitOrder order; | ||||
|         // The order signature. | ||||
|         LibSignature.Signature signature; | ||||
|         // Maximum taker token to fill this order with. | ||||
|         uint128 takerTokenFillAmount; | ||||
|         // The order taker. | ||||
|         address taker; | ||||
|         // The order sender. | ||||
|         address sender; | ||||
|     } | ||||
|  | ||||
|     // @dev Fill results returned by `_fillLimitOrderPrivate()` and | ||||
|     ///     `_fillRfqOrderPrivate()`. | ||||
|     struct FillNativeOrderResults { | ||||
|         uint256 ethProtocolFeePaid; | ||||
|         uint128 takerTokenFilledAmount; | ||||
|         uint128 makerTokenFilledAmount; | ||||
|         uint128 takerTokenFeeFilledAmount; | ||||
|     } | ||||
|  | ||||
|     constructor( | ||||
|         address zeroExAddress, | ||||
|         IEtherTokenV06 weth, | ||||
|         IStaking staking, | ||||
|         FeeCollectorController feeCollectorController, | ||||
|         uint32 protocolFeeMultiplier, | ||||
|         bytes32 greedyTokensBloomFilter | ||||
|     ) | ||||
|         public | ||||
|         NativeOrdersCancellation(zeroExAddress, greedyTokensBloomFilter) | ||||
|         NativeOrdersProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier) | ||||
|     { | ||||
|         // solhint-disable no-empty-blocks | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill a limit order. The taker and sender will be the caller. | ||||
|     /// @param order The limit order. ETH protocol fees can be | ||||
|     ///      attached to this call. Any unspent ETH will be refunded to | ||||
|     ///      the caller. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount Maximum taker token amount to fill this order with. | ||||
|     /// @return takerTokenFilledAmount How much maker token was filled. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function fillLimitOrder( | ||||
|         LibNativeOrder.LimitOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount | ||||
|     ) | ||||
|         public | ||||
|         payable | ||||
|         returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillLimitOrderPrivate(FillLimitOrderPrivateParams({ | ||||
|                 order: order, | ||||
|                 signature: signature, | ||||
|                 takerTokenFillAmount: takerTokenFillAmount, | ||||
|                 taker: msg.sender, | ||||
|                 sender: msg.sender | ||||
|             })); | ||||
|         LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid); | ||||
|         (takerTokenFilledAmount, makerTokenFilledAmount) = ( | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill an RFQ order for up to `takerTokenFillAmount` taker tokens. | ||||
|     ///      The taker will be the caller. ETH should be attached to pay the | ||||
|     ///      protocol fee. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount Maximum taker token amount to fill this order with. | ||||
|     /// @return takerTokenFilledAmount How much maker token was filled. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function fillRfqOrder( | ||||
|         LibNativeOrder.RfqOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount | ||||
|     ) | ||||
|         public | ||||
|         returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillRfqOrderPrivate( | ||||
|                 order, | ||||
|                 signature, | ||||
|                 takerTokenFillAmount, | ||||
|                 msg.sender | ||||
|             ); | ||||
|         (takerTokenFilledAmount, makerTokenFilledAmount) = ( | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens. | ||||
|     ///      The taker will be the caller. ETH protocol fees can be | ||||
|     ///      attached to this call. Any unspent ETH will be refunded to | ||||
|     ///      the caller. | ||||
|     /// @param order The limit order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount How much taker token to fill this order with. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function fillOrKillLimitOrder( | ||||
|         LibNativeOrder.LimitOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount | ||||
|     ) | ||||
|         public | ||||
|         payable | ||||
|         returns (uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillLimitOrderPrivate(FillLimitOrderPrivateParams({ | ||||
|                 order: order, | ||||
|                 signature: signature, | ||||
|                 takerTokenFillAmount: takerTokenFillAmount, | ||||
|                 taker: msg.sender, | ||||
|                 sender: msg.sender | ||||
|             })); | ||||
|         // Must have filled exactly the amount requested. | ||||
|         if (results.takerTokenFilledAmount < takerTokenFillAmount) { | ||||
|             LibNativeOrdersRichErrors.FillOrKillFailedError( | ||||
|                 getLimitOrderHash(order), | ||||
|                 results.takerTokenFilledAmount, | ||||
|                 takerTokenFillAmount | ||||
|             ).rrevert(); | ||||
|         } | ||||
|         LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid); | ||||
|         makerTokenFilledAmount = results.makerTokenFilledAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens. | ||||
|     ///      The taker will be the caller. ETH protocol fees can be | ||||
|     ///      attached to this call. Any unspent ETH will be refunded to | ||||
|     ///      the caller. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount How much taker token to fill this order with. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function fillOrKillRfqOrder( | ||||
|         LibNativeOrder.RfqOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount | ||||
|     ) | ||||
|         public | ||||
|         returns (uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillRfqOrderPrivate( | ||||
|                 order, | ||||
|                 signature, | ||||
|                 takerTokenFillAmount, | ||||
|                 msg.sender | ||||
|             ); | ||||
|         // Must have filled exactly the amount requested. | ||||
|         if (results.takerTokenFilledAmount < takerTokenFillAmount) { | ||||
|             LibNativeOrdersRichErrors.FillOrKillFailedError( | ||||
|                 getRfqOrderHash(order), | ||||
|                 results.takerTokenFilledAmount, | ||||
|                 takerTokenFillAmount | ||||
|             ).rrevert(); | ||||
|         } | ||||
|         makerTokenFilledAmount = results.makerTokenFilledAmount; | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill a limit order. Internal variant. ETH protocol fees can be | ||||
|     ///      attached to this call. | ||||
|     /// @param order The limit order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount Maximum taker token to fill this order with. | ||||
|     /// @param taker The order taker. | ||||
|     /// @param sender The order sender. | ||||
|     /// @return takerTokenFilledAmount How much maker token was filled. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function _fillLimitOrder( | ||||
|         LibNativeOrder.LimitOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount, | ||||
|         address taker, | ||||
|         address sender | ||||
|     ) | ||||
|         public | ||||
|         virtual | ||||
|         payable | ||||
|         onlySelf | ||||
|         returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillLimitOrderPrivate(FillLimitOrderPrivateParams({ | ||||
|                 order: order, | ||||
|                 signature: signature, | ||||
|                 takerTokenFillAmount: takerTokenFillAmount, | ||||
|                 taker: taker, | ||||
|                 sender: sender | ||||
|             })); | ||||
|         (takerTokenFilledAmount, makerTokenFilledAmount) = ( | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill an RFQ order. Internal variant. ETH protocol fees can be | ||||
|     ///      attached to this call. Any unspent ETH will be refunded to | ||||
|     ///      `msg.sender` (not `sender`). | ||||
|     /// @param order The RFQ order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount Maximum taker token to fill this order with. | ||||
|     /// @param taker The order taker. | ||||
|     /// @return takerTokenFilledAmount How much maker token was filled. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function _fillRfqOrder( | ||||
|         LibNativeOrder.RfqOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount, | ||||
|         address taker | ||||
|     ) | ||||
|         public | ||||
|         virtual | ||||
|         onlySelf | ||||
|         returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         FillNativeOrderResults memory results = | ||||
|             _fillRfqOrderPrivate( | ||||
|                 order, | ||||
|                 signature, | ||||
|                 takerTokenFillAmount, | ||||
|                 taker | ||||
|             ); | ||||
|         (takerTokenFilledAmount, makerTokenFilledAmount) = ( | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Mark what tx.origin addresses are allowed to fill an order that | ||||
|     ///      specifies the message sender as its txOrigin. | ||||
|     /// @param origins An array of origin addresses to update. | ||||
|     /// @param allowed True to register, false to unregister. | ||||
|     function registerAllowedRfqOrigins( | ||||
|         address[] memory origins, | ||||
|         bool allowed | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         require(msg.sender == tx.origin, | ||||
|             "NativeOrdersFeature/NO_CONTRACT_ORIGINS"); | ||||
|  | ||||
|         LibNativeOrdersStorage.Storage storage stor = | ||||
|             LibNativeOrdersStorage.getStorage(); | ||||
|  | ||||
|         for (uint256 i = 0; i < origins.length; i++) { | ||||
|             stor.originRegistry[msg.sender][origins[i]] = allowed; | ||||
|         } | ||||
|  | ||||
|         emit RfqOrderOriginsAllowed(msg.sender, origins, allowed); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill a limit order. Private variant. Does not refund protocol fees. | ||||
|     /// @param params Function params. | ||||
|     /// @return results Results of the fill. | ||||
|     function _fillLimitOrderPrivate(FillLimitOrderPrivateParams memory params) | ||||
|         private | ||||
|         returns (FillNativeOrderResults memory results) | ||||
|     { | ||||
|         LibNativeOrder.OrderInfo memory orderInfo = getLimitOrderInfo(params.order); | ||||
|  | ||||
|         // Must be fillable. | ||||
|         if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) { | ||||
|             LibNativeOrdersRichErrors.OrderNotFillableError( | ||||
|                 orderInfo.orderHash, | ||||
|                 uint8(orderInfo.status) | ||||
|             ).rrevert(); | ||||
|         } | ||||
|  | ||||
|         // Must be fillable by the taker. | ||||
|         if (params.order.taker != address(0) && params.order.taker != params.taker) { | ||||
|             LibNativeOrdersRichErrors.OrderNotFillableByTakerError( | ||||
|                 orderInfo.orderHash, | ||||
|                 params.taker, | ||||
|                 params.order.taker | ||||
|             ).rrevert(); | ||||
|         } | ||||
|  | ||||
|         // Must be fillable by the sender. | ||||
|         if (params.order.sender != address(0) && params.order.sender != params.sender) { | ||||
|             LibNativeOrdersRichErrors.OrderNotFillableBySenderError( | ||||
|                 orderInfo.orderHash, | ||||
|                 params.sender, | ||||
|                 params.order.sender | ||||
|             ).rrevert(); | ||||
|         } | ||||
|  | ||||
|         // Signature must be valid for the order. | ||||
|         { | ||||
|             address signer = LibSignature.getSignerOfHash( | ||||
|                 orderInfo.orderHash, | ||||
|                 params.signature | ||||
|             ); | ||||
|             if (signer != params.order.maker) { | ||||
|                 LibNativeOrdersRichErrors.OrderNotSignedByMakerError( | ||||
|                     orderInfo.orderHash, | ||||
|                     signer, | ||||
|                     params.order.maker | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Pay the protocol fee. | ||||
|         results.ethProtocolFeePaid = _collectProtocolFee(params.order.pool); | ||||
|  | ||||
|         // Settle between the maker and taker. | ||||
|         (results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder( | ||||
|             SettleOrderInfo({ | ||||
|                 orderHash: orderInfo.orderHash, | ||||
|                 maker: params.order.maker, | ||||
|                 taker: params.taker, | ||||
|                 makerToken: IERC20TokenV06(params.order.makerToken), | ||||
|                 takerToken: IERC20TokenV06(params.order.takerToken), | ||||
|                 makerAmount: params.order.makerAmount, | ||||
|                 takerAmount: params.order.takerAmount, | ||||
|                 takerTokenFillAmount: params.takerTokenFillAmount, | ||||
|                 takerTokenFilledAmount: orderInfo.takerTokenFilledAmount | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         // Pay the fee recipient. | ||||
|         if (params.order.takerTokenFeeAmount > 0) { | ||||
|             results.takerTokenFeeFilledAmount = uint128(LibMathV06.getPartialAmountFloor( | ||||
|                 results.takerTokenFilledAmount, | ||||
|                 params.order.takerAmount, | ||||
|                 params.order.takerTokenFeeAmount | ||||
|             )); | ||||
|             _transferERC20Tokens( | ||||
|                 params.order.takerToken, | ||||
|                 params.taker, | ||||
|                 params.order.feeRecipient, | ||||
|                 uint256(results.takerTokenFeeFilledAmount) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         emit LimitOrderFilled( | ||||
|             orderInfo.orderHash, | ||||
|             params.order.maker, | ||||
|             params.taker, | ||||
|             params.order.feeRecipient, | ||||
|             address(params.order.makerToken), | ||||
|             address(params.order.takerToken), | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount, | ||||
|             results.takerTokenFeeFilledAmount, | ||||
|             results.ethProtocolFeePaid, | ||||
|             params.order.pool | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Fill an RFQ order. Private variant. Does not refund protocol fees. | ||||
|     /// @param order The RFQ order. | ||||
|     /// @param signature The order signature. | ||||
|     /// @param takerTokenFillAmount Maximum taker token to fill this order with. | ||||
|     /// @param taker The order taker. | ||||
|     /// @return results Results of the fill. | ||||
|     function _fillRfqOrderPrivate( | ||||
|         LibNativeOrder.RfqOrder memory order, | ||||
|         LibSignature.Signature memory signature, | ||||
|         uint128 takerTokenFillAmount, | ||||
|         address taker | ||||
|     ) | ||||
|         private | ||||
|         returns (FillNativeOrderResults memory results) | ||||
|     { | ||||
|         LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(order); | ||||
|  | ||||
|         // Must be fillable. | ||||
|         if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) { | ||||
|             LibNativeOrdersRichErrors.OrderNotFillableError( | ||||
|                 orderInfo.orderHash, | ||||
|                 uint8(orderInfo.status) | ||||
|             ).rrevert(); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             LibNativeOrdersStorage.Storage storage stor = | ||||
|                 LibNativeOrdersStorage.getStorage(); | ||||
|  | ||||
|             // Must be fillable by the tx.origin. | ||||
|             if (order.txOrigin != tx.origin && !stor.originRegistry[order.txOrigin][tx.origin]) { | ||||
|                 LibNativeOrdersRichErrors.OrderNotFillableByOriginError( | ||||
|                     orderInfo.orderHash, | ||||
|                     tx.origin, | ||||
|                     order.txOrigin | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Must be fillable by the taker. | ||||
|         if (order.taker != address(0) && order.taker != taker) { | ||||
|             LibNativeOrdersRichErrors.OrderNotFillableByTakerError( | ||||
|                 orderInfo.orderHash, | ||||
|                 taker, | ||||
|                 order.taker | ||||
|             ).rrevert(); | ||||
|         } | ||||
|  | ||||
|         // Signature must be valid for the order. | ||||
|         { | ||||
|             address signer = LibSignature.getSignerOfHash(orderInfo.orderHash, signature); | ||||
|             if (signer != order.maker) { | ||||
|                 LibNativeOrdersRichErrors.OrderNotSignedByMakerError( | ||||
|                     orderInfo.orderHash, | ||||
|                     signer, | ||||
|                     order.maker | ||||
|                 ).rrevert(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Settle between the maker and taker. | ||||
|         (results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder( | ||||
|             SettleOrderInfo({ | ||||
|                 orderHash: orderInfo.orderHash, | ||||
|                 maker: order.maker, | ||||
|                 taker: taker, | ||||
|                 makerToken: IERC20TokenV06(order.makerToken), | ||||
|                 takerToken: IERC20TokenV06(order.takerToken), | ||||
|                 makerAmount: order.makerAmount, | ||||
|                 takerAmount: order.takerAmount, | ||||
|                 takerTokenFillAmount: takerTokenFillAmount, | ||||
|                 takerTokenFilledAmount: orderInfo.takerTokenFilledAmount | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         emit RfqOrderFilled( | ||||
|             orderInfo.orderHash, | ||||
|             order.maker, | ||||
|             taker, | ||||
|             address(order.makerToken), | ||||
|             address(order.takerToken), | ||||
|             results.takerTokenFilledAmount, | ||||
|             results.makerTokenFilledAmount, | ||||
|             order.pool | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Settle the trade between an order's maker and taker. | ||||
|     /// @param settleInfo Information needed to execute the settlement. | ||||
|     /// @return takerTokenFilledAmount How much taker token was filled. | ||||
|     /// @return makerTokenFilledAmount How much maker token was filled. | ||||
|     function _settleOrder(SettleOrderInfo memory settleInfo) | ||||
|         private | ||||
|         returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) | ||||
|     { | ||||
|         // Clamp the taker token fill amount to the fillable amount. | ||||
|         takerTokenFilledAmount = LibSafeMathV06.min128( | ||||
|             settleInfo.takerTokenFillAmount, | ||||
|             settleInfo.takerAmount.safeSub128(settleInfo.takerTokenFilledAmount) | ||||
|         ); | ||||
|         // Compute the maker token amount. | ||||
|         // This should never overflow because the values are all clamped to | ||||
|         // (2^128-1). | ||||
|         makerTokenFilledAmount = uint128(LibMathV06.getPartialAmountFloor( | ||||
|             uint256(takerTokenFilledAmount), | ||||
|             uint256(settleInfo.takerAmount), | ||||
|             uint256(settleInfo.makerAmount) | ||||
|         )); | ||||
|  | ||||
|         if (takerTokenFilledAmount == 0 || makerTokenFilledAmount == 0) { | ||||
|             // Nothing to do. | ||||
|             return (0, 0); | ||||
|         } | ||||
|  | ||||
|         // Update filled state for the order. | ||||
|         LibNativeOrdersStorage | ||||
|             .getStorage() | ||||
|             .orderHashToTakerTokenFilledAmount[settleInfo.orderHash] = | ||||
|             // OK to overwrite the whole word because we shouldn't get to this | ||||
|             // function if the order is cancelled. | ||||
|                 settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount); | ||||
|  | ||||
|         // Transfer taker -> maker. | ||||
|         _transferERC20Tokens( | ||||
|             settleInfo.takerToken, | ||||
|             settleInfo.taker, | ||||
|             settleInfo.maker, | ||||
|             takerTokenFilledAmount | ||||
|         ); | ||||
|  | ||||
|         // Transfer maker -> taker. | ||||
|         _transferERC20Tokens( | ||||
|             settleInfo.makerToken, | ||||
|             settleInfo.maker, | ||||
|             settleInfo.taker, | ||||
|             makerTokenFilledAmount | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -23,8 +23,8 @@ pragma experimental ABIEncoderV2; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
| import "../errors/LibCommonRichErrors.sol"; | ||||
| import "../errors/LibOwnableRichErrors.sol"; | ||||
| import "../features/IOwnableFeature.sol"; | ||||
| import "../features/ISimpleFunctionRegistryFeature.sol"; | ||||
| import "../features/interfaces/IOwnableFeature.sol"; | ||||
| import "../features/interfaces/ISimpleFunctionRegistryFeature.sol"; | ||||
|  | ||||
|  | ||||
| /// @dev Common feature utilities. | ||||
|   | ||||
| @@ -22,7 +22,7 @@ pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; | ||||
| import "../features/ITokenSpenderFeature.sol"; | ||||
| import "../features/interfaces/ITokenSpenderFeature.sol"; | ||||
| import "../errors/LibSpenderRichErrors.sol"; | ||||
| import "../external/FeeCollector.sol"; | ||||
| import "../vendor/v3/IStaking.sol"; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../ZeroEx.sol"; | ||||
| import "../features/IOwnableFeature.sol"; | ||||
| import "../features/interfaces/IOwnableFeature.sol"; | ||||
| import "../features/TokenSpenderFeature.sol"; | ||||
| import "../features/TransformERC20Feature.sol"; | ||||
| import "../features/MetaTransactionsFeature.sol"; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../ZeroEx.sol"; | ||||
| import "../features/IBootstrapFeature.sol"; | ||||
| import "../features/interfaces/IBootstrapFeature.sol"; | ||||
| import "../features/SimpleFunctionRegistryFeature.sol"; | ||||
| import "../features/OwnableFeature.sol"; | ||||
| import "./LibBootstrap.sol"; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ 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 "../features/INativeOrdersFeature.sol"; | ||||
| import "../features/interfaces/INativeOrdersFeature.sol"; | ||||
| import "../features/libs/LibNativeOrder.sol"; | ||||
| import "./bridges/IBridgeAdapter.sol"; | ||||
| import "./Transformer.sol"; | ||||
| @@ -132,8 +132,6 @@ contract FillQuoteTransformer is | ||||
|     /// @param orderHash The hash of the order that was skipped. | ||||
|     event ProtocolFeeUnfunded(bytes32 orderHash); | ||||
|  | ||||
|     /// @dev Maximum uint256 value. | ||||
|     uint256 private constant MAX_UINT256 = uint256(-1); | ||||
|     /// @dev The highest bit of a uint256 value. | ||||
|     uint256 private constant HIGH_BIT = 2 ** 255; | ||||
|     /// @dev Mask of the lower 255 bits of a uint256 value. | ||||
|   | ||||
							
								
								
									
										48
									
								
								contracts/zero-ex/contracts/src/vendor/IUniswapV2Pair.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								contracts/zero-ex/contracts/src/vendor/IUniswapV2Pair.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2021 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.12; | ||||
|  | ||||
|  | ||||
| interface IUniswapV2Pair { | ||||
|     event Swap( | ||||
|         address indexed sender, | ||||
|         uint256 amount0In, | ||||
|         uint256 amount1In, | ||||
|         uint256 amount0Out, | ||||
|         uint256 amount1Out, | ||||
|         address indexed to | ||||
|     ); | ||||
|  | ||||
|     function swap( | ||||
|         uint amount0Out, | ||||
|         uint amount1Out, | ||||
|         address to, | ||||
|         bytes calldata data | ||||
|     ) external; | ||||
|  | ||||
|     function getReserves() | ||||
|         external | ||||
|         view | ||||
|         returns ( | ||||
|             uint112 reserve0, | ||||
|             uint112 reserve1, | ||||
|             uint32 blockTimestampLast | ||||
|         ); | ||||
| } | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/ZeroEx.sol"; | ||||
| import "../src/features/IBootstrapFeature.sol"; | ||||
| import "../src/features/interfaces/IBootstrapFeature.sol"; | ||||
| import "../src/migrations/InitialMigration.sol"; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -20,8 +20,8 @@ | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/features/interfaces/IMetaTransactionsFeature.sol"; | ||||
| import "../src/features/NativeOrdersFeature.sol"; | ||||
| import "../src/features/IMetaTransactionsFeature.sol"; | ||||
| import "./TestFeeCollectorController.sol"; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -20,8 +20,8 @@ | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/features/interfaces/IMetaTransactionsFeature.sol"; | ||||
| import "../src/features/TransformERC20Feature.sol"; | ||||
| import "../src/features/IMetaTransactionsFeature.sol"; | ||||
|  | ||||
|  | ||||
| contract TestMetaTransactionsTransformERC20Feature is | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/migrations/LibMigrate.sol"; | ||||
| import "../src/features/IOwnableFeature.sol"; | ||||
| import "../src/features/interfaces/IOwnableFeature.sol"; | ||||
|  | ||||
|  | ||||
| contract TestMigrator { | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| pragma solidity ^0.6.5; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "../src/features/INativeOrdersFeature.sol"; | ||||
| import "../src/features/interfaces/INativeOrdersFeature.sol"; | ||||
|  | ||||
| contract TestRfqOriginRegistration { | ||||
|     function registerAllowedRfqOrigins( | ||||
|   | ||||
| @@ -41,9 +41,9 @@ | ||||
|         "rollback": "node ./lib/scripts/rollback.js" | ||||
|     }, | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider", | ||||
|         "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", | ||||
|         "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json" | ||||
|         "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json" | ||||
|     }, | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as AffiliateFeeTransformer from '../generated-artifacts/AffiliateFeeTransformer.json'; | ||||
| import * as BatchFillNativeOrdersFeature from '../generated-artifacts/BatchFillNativeOrdersFeature.json'; | ||||
| import * as BridgeAdapter from '../generated-artifacts/BridgeAdapter.json'; | ||||
| import * as CurveLiquidityProvider from '../generated-artifacts/CurveLiquidityProvider.json'; | ||||
| import * as FeeCollector from '../generated-artifacts/FeeCollector.json'; | ||||
| @@ -13,9 +14,11 @@ import * as FeeCollectorController from '../generated-artifacts/FeeCollectorCont | ||||
| 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 IBatchFillNativeOrdersFeature from '../generated-artifacts/IBatchFillNativeOrdersFeature.json'; | ||||
| import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json'; | ||||
| import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json'; | ||||
| import * as ILiquidityProviderFeature from '../generated-artifacts/ILiquidityProviderFeature.json'; | ||||
| import * as IMultiplexFeature from '../generated-artifacts/IMultiplexFeature.json'; | ||||
| import * as INativeOrdersFeature from '../generated-artifacts/INativeOrdersFeature.json'; | ||||
| import * as InitialMigration from '../generated-artifacts/InitialMigration.json'; | ||||
| import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json'; | ||||
| @@ -26,6 +29,7 @@ import * as IZeroEx from '../generated-artifacts/IZeroEx.json'; | ||||
| import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json'; | ||||
| import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json'; | ||||
| import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json'; | ||||
| import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json'; | ||||
| import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json'; | ||||
| import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json'; | ||||
| import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json'; | ||||
| @@ -66,4 +70,8 @@ export const artifacts = { | ||||
|     FeeCollectorController: FeeCollectorController as ContractArtifact, | ||||
|     FeeCollector: FeeCollector as ContractArtifact, | ||||
|     CurveLiquidityProvider: CurveLiquidityProvider as ContractArtifact, | ||||
|     BatchFillNativeOrdersFeature: BatchFillNativeOrdersFeature as ContractArtifact, | ||||
|     IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact, | ||||
|     MultiplexFeature: MultiplexFeature as ContractArtifact, | ||||
|     IMultiplexFeature: IMultiplexFeature as ContractArtifact, | ||||
| }; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../generated-wrappers/affiliate_fee_transformer'; | ||||
| export * from '../generated-wrappers/batch_fill_native_orders_feature'; | ||||
| export * from '../generated-wrappers/bridge_adapter'; | ||||
| export * from '../generated-wrappers/curve_liquidity_provider'; | ||||
| export * from '../generated-wrappers/fee_collector'; | ||||
| @@ -11,9 +12,11 @@ export * from '../generated-wrappers/fee_collector_controller'; | ||||
| 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_batch_fill_native_orders_feature'; | ||||
| export * from '../generated-wrappers/i_erc20_transformer'; | ||||
| export * from '../generated-wrappers/i_flash_wallet'; | ||||
| export * from '../generated-wrappers/i_liquidity_provider_feature'; | ||||
| export * from '../generated-wrappers/i_multiplex_feature'; | ||||
| export * from '../generated-wrappers/i_native_orders_feature'; | ||||
| export * from '../generated-wrappers/i_ownable_feature'; | ||||
| export * from '../generated-wrappers/i_simple_function_registry_feature'; | ||||
| @@ -24,6 +27,7 @@ export * from '../generated-wrappers/initial_migration'; | ||||
| export * from '../generated-wrappers/liquidity_provider_feature'; | ||||
| export * from '../generated-wrappers/log_metadata_transformer'; | ||||
| export * from '../generated-wrappers/meta_transactions_feature'; | ||||
| export * from '../generated-wrappers/multiplex_feature'; | ||||
| export * from '../generated-wrappers/native_orders_feature'; | ||||
| export * from '../generated-wrappers/ownable_feature'; | ||||
| export * from '../generated-wrappers/pay_taker_transformer'; | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json'; | ||||
| import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json'; | ||||
| import * as BatchFillNativeOrdersFeature from '../test/generated-artifacts/BatchFillNativeOrdersFeature.json'; | ||||
| import * as BootstrapFeature from '../test/generated-artifacts/BootstrapFeature.json'; | ||||
| import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json'; | ||||
| import * as BridgeSource from '../test/generated-artifacts/BridgeSource.json'; | ||||
| @@ -22,6 +23,7 @@ import * as FixinTokenSpender from '../test/generated-artifacts/FixinTokenSpende | ||||
| 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 IBatchFillNativeOrdersFeature from '../test/generated-artifacts/IBatchFillNativeOrdersFeature.json'; | ||||
| import * as IBootstrapFeature from '../test/generated-artifacts/IBootstrapFeature.json'; | ||||
| import * as IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.json'; | ||||
| import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json'; | ||||
| @@ -33,6 +35,8 @@ import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidi | ||||
| import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json'; | ||||
| import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json'; | ||||
| import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json'; | ||||
| import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json'; | ||||
| import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json'; | ||||
| import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json'; | ||||
| import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json'; | ||||
| import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json'; | ||||
| @@ -42,6 +46,7 @@ import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts | ||||
| import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json'; | ||||
| import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json'; | ||||
| import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json'; | ||||
| import * as IUniswapV2Pair from '../test/generated-artifacts/IUniswapV2Pair.json'; | ||||
| import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json'; | ||||
| import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json'; | ||||
| import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json'; | ||||
| @@ -90,7 +95,12 @@ import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json'; | ||||
| import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json'; | ||||
| import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json'; | ||||
| import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json'; | ||||
| import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json'; | ||||
| import * as NativeOrdersCancellation from '../test/generated-artifacts/NativeOrdersCancellation.json'; | ||||
| import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json'; | ||||
| import * as NativeOrdersInfo from '../test/generated-artifacts/NativeOrdersInfo.json'; | ||||
| import * as NativeOrdersProtocolFees from '../test/generated-artifacts/NativeOrdersProtocolFees.json'; | ||||
| import * as NativeOrdersSettlement from '../test/generated-artifacts/NativeOrdersSettlement.json'; | ||||
| import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json'; | ||||
| import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json'; | ||||
| import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.json'; | ||||
| @@ -167,27 +177,36 @@ export const artifacts = { | ||||
|     LiquidityProviderSandbox: LiquidityProviderSandbox as ContractArtifact, | ||||
|     PermissionlessTransformerDeployer: PermissionlessTransformerDeployer as ContractArtifact, | ||||
|     TransformerDeployer: TransformerDeployer as ContractArtifact, | ||||
|     BatchFillNativeOrdersFeature: BatchFillNativeOrdersFeature as ContractArtifact, | ||||
|     BootstrapFeature: BootstrapFeature as ContractArtifact, | ||||
|     IBootstrapFeature: IBootstrapFeature as ContractArtifact, | ||||
|     IFeature: IFeature as ContractArtifact, | ||||
|     ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact, | ||||
|     IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact, | ||||
|     INativeOrdersFeature: INativeOrdersFeature as ContractArtifact, | ||||
|     IOwnableFeature: IOwnableFeature as ContractArtifact, | ||||
|     ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact, | ||||
|     ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact, | ||||
|     ITransformERC20Feature: ITransformERC20Feature as ContractArtifact, | ||||
|     IUniswapFeature: IUniswapFeature as ContractArtifact, | ||||
|     LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact, | ||||
|     MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact, | ||||
|     MultiplexFeature: MultiplexFeature as ContractArtifact, | ||||
|     NativeOrdersFeature: NativeOrdersFeature as ContractArtifact, | ||||
|     OwnableFeature: OwnableFeature as ContractArtifact, | ||||
|     SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact, | ||||
|     TokenSpenderFeature: TokenSpenderFeature as ContractArtifact, | ||||
|     TransformERC20Feature: TransformERC20Feature as ContractArtifact, | ||||
|     UniswapFeature: UniswapFeature as ContractArtifact, | ||||
|     IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact, | ||||
|     IBootstrapFeature: IBootstrapFeature as ContractArtifact, | ||||
|     IFeature: IFeature as ContractArtifact, | ||||
|     ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact, | ||||
|     IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact, | ||||
|     IMultiplexFeature: IMultiplexFeature as ContractArtifact, | ||||
|     INativeOrdersEvents: INativeOrdersEvents as ContractArtifact, | ||||
|     INativeOrdersFeature: INativeOrdersFeature as ContractArtifact, | ||||
|     IOwnableFeature: IOwnableFeature as ContractArtifact, | ||||
|     ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact, | ||||
|     ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact, | ||||
|     ITransformERC20Feature: ITransformERC20Feature as ContractArtifact, | ||||
|     IUniswapFeature: IUniswapFeature as ContractArtifact, | ||||
|     LibNativeOrder: LibNativeOrder as ContractArtifact, | ||||
|     LibSignature: LibSignature as ContractArtifact, | ||||
|     NativeOrdersCancellation: NativeOrdersCancellation as ContractArtifact, | ||||
|     NativeOrdersInfo: NativeOrdersInfo as ContractArtifact, | ||||
|     NativeOrdersProtocolFees: NativeOrdersProtocolFees as ContractArtifact, | ||||
|     NativeOrdersSettlement: NativeOrdersSettlement as ContractArtifact, | ||||
|     FixinCommon: FixinCommon as ContractArtifact, | ||||
|     FixinEIP712: FixinEIP712 as ContractArtifact, | ||||
|     FixinProtocolFees: FixinProtocolFees as ContractArtifact, | ||||
| @@ -238,6 +257,7 @@ export const artifacts = { | ||||
|     MixinZeroExBridge: MixinZeroExBridge as ContractArtifact, | ||||
|     ILiquidityProvider: ILiquidityProvider as ContractArtifact, | ||||
|     IMooniswapPool: IMooniswapPool as ContractArtifact, | ||||
|     IUniswapV2Pair: IUniswapV2Pair as ContractArtifact, | ||||
|     IERC20Bridge: IERC20Bridge as ContractArtifact, | ||||
|     IStaking: IStaking as ContractArtifact, | ||||
|     ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact, | ||||
|   | ||||
							
								
								
									
										479
									
								
								contracts/zero-ex/test/features/batch_fill_native_orders_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								contracts/zero-ex/test/features/batch_fill_native_orders_test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,479 @@ | ||||
| import { | ||||
|     blockchainTests, | ||||
|     constants, | ||||
|     describe, | ||||
|     expect, | ||||
|     getRandomPortion, | ||||
|     verifyEventsFromLogs, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { LimitOrder, LimitOrderFields, OrderStatus, RevertErrors, RfqOrder, RfqOrderFields } from '@0x/protocol-utils'; | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { BatchFillNativeOrdersFeatureContract, IZeroExContract, IZeroExEvents } from '../../src/wrappers'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { abis } from '../utils/abis'; | ||||
| import { | ||||
|     assertOrderInfoEquals, | ||||
|     computeLimitOrderFilledAmounts, | ||||
|     computeRfqOrderFilledAmounts, | ||||
|     createExpiry, | ||||
|     getRandomLimitOrder, | ||||
|     getRandomRfqOrder, | ||||
|     NativeOrdersTestEnvironment, | ||||
| } from '../utils/orders'; | ||||
| import { TestMintableERC20TokenContract } from '../wrappers'; | ||||
|  | ||||
| blockchainTests.resets('BatchFillNativeOrdersFeature', env => { | ||||
|     const { NULL_ADDRESS, ZERO_AMOUNT } = constants; | ||||
|     let maker: string; | ||||
|     let taker: string; | ||||
|     let zeroEx: IZeroExContract; | ||||
|     let feature: BatchFillNativeOrdersFeatureContract; | ||||
|     let verifyingContract: string; | ||||
|     let makerToken: TestMintableERC20TokenContract; | ||||
|     let takerToken: TestMintableERC20TokenContract; | ||||
|     let testUtils: NativeOrdersTestEnvironment; | ||||
|  | ||||
|     before(async () => { | ||||
|         testUtils = await NativeOrdersTestEnvironment.createAsync(env); | ||||
|         maker = testUtils.maker; | ||||
|         taker = testUtils.taker; | ||||
|         zeroEx = testUtils.zeroEx; | ||||
|         makerToken = testUtils.makerToken; | ||||
|         takerToken = testUtils.takerToken; | ||||
|  | ||||
|         verifyingContract = zeroEx.address; | ||||
|         const featureImpl = await BatchFillNativeOrdersFeatureContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.BatchFillNativeOrdersFeature, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             zeroEx.address, | ||||
|         ); | ||||
|         const [owner] = await env.getAccountAddressesAsync(); | ||||
|         await zeroEx | ||||
|             .migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         feature = new BatchFillNativeOrdersFeatureContract( | ||||
|             zeroEx.address, | ||||
|             env.provider, | ||||
|             { ...env.txDefaults, gasPrice: testUtils.gasPrice }, | ||||
|             abis, | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     function getTestLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder { | ||||
|         return getRandomLimitOrder({ | ||||
|             maker, | ||||
|             verifyingContract, | ||||
|             chainId: 1337, | ||||
|             takerToken: takerToken.address, | ||||
|             makerToken: makerToken.address, | ||||
|             taker: NULL_ADDRESS, | ||||
|             sender: NULL_ADDRESS, | ||||
|             ...fields, | ||||
|         }); | ||||
|     } | ||||
|     function getTestRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrder { | ||||
|         return getRandomRfqOrder({ | ||||
|             maker, | ||||
|             verifyingContract, | ||||
|             chainId: 1337, | ||||
|             takerToken: takerToken.address, | ||||
|             makerToken: makerToken.address, | ||||
|             txOrigin: taker, | ||||
|             ...fields, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     describe('batchFillLimitOrders', () => { | ||||
|         async function assertExpectedFinalBalancesAsync( | ||||
|             orders: LimitOrder[], | ||||
|             takerTokenFillAmounts: BigNumber[] = orders.map(order => order.takerAmount), | ||||
|             takerTokenAlreadyFilledAmounts: BigNumber[] = orders.map(() => ZERO_AMOUNT), | ||||
|             receipt?: TransactionReceiptWithDecodedLogs, | ||||
|         ): Promise<void> { | ||||
|             const expectedFeeRecipientBalances: { [feeRecipient: string]: BigNumber } = {}; | ||||
|             const { makerTokenFilledAmount, takerTokenFilledAmount } = orders | ||||
|                 .map((order, i) => | ||||
|                     computeLimitOrderFilledAmounts(order, takerTokenFillAmounts[i], takerTokenAlreadyFilledAmounts[i]), | ||||
|                 ) | ||||
|                 .reduce( | ||||
|                     (previous, current, i) => { | ||||
|                         _.update(expectedFeeRecipientBalances, orders[i].feeRecipient, balance => | ||||
|                             (balance || ZERO_AMOUNT).plus(current.takerTokenFeeFilledAmount), | ||||
|                         ); | ||||
|                         return { | ||||
|                             makerTokenFilledAmount: previous.makerTokenFilledAmount.plus( | ||||
|                                 current.makerTokenFilledAmount, | ||||
|                             ), | ||||
|                             takerTokenFilledAmount: previous.takerTokenFilledAmount.plus( | ||||
|                                 current.takerTokenFilledAmount, | ||||
|                             ), | ||||
|                         }; | ||||
|                     }, | ||||
|                     { makerTokenFilledAmount: ZERO_AMOUNT, takerTokenFilledAmount: ZERO_AMOUNT }, | ||||
|                 ); | ||||
|             const makerBalance = await takerToken.balanceOf(maker).callAsync(); | ||||
|             const takerBalance = await makerToken.balanceOf(taker).callAsync(); | ||||
|             expect(makerBalance, 'maker token balance').to.bignumber.eq(takerTokenFilledAmount); | ||||
|             expect(takerBalance, 'taker token balance').to.bignumber.eq(makerTokenFilledAmount); | ||||
|             for (const [feeRecipient, expectedFeeRecipientBalance] of Object.entries(expectedFeeRecipientBalances)) { | ||||
|                 const feeRecipientBalance = await takerToken.balanceOf(feeRecipient).callAsync(); | ||||
|                 expect(feeRecipientBalance, `fee recipient balance`).to.bignumber.eq(expectedFeeRecipientBalance); | ||||
|             } | ||||
|             if (receipt) { | ||||
|                 const balanceOfTakerNow = await env.web3Wrapper.getBalanceInWeiAsync(taker); | ||||
|                 const balanceOfTakerBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker, receipt.blockNumber - 1); | ||||
|                 const protocolFees = testUtils.protocolFee.times(orders.length); | ||||
|                 const totalCost = testUtils.gasPrice.times(receipt.gasUsed).plus(protocolFees); | ||||
|                 expect(balanceOfTakerBefore.minus(totalCost), 'taker ETH balance').to.bignumber.eq(balanceOfTakerNow); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         it('Fully fills multiple orders', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const tx = await feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: orders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders); | ||||
|         }); | ||||
|         it('Partially fills multiple orders', async () => { | ||||
|             const orders = [...new Array(3)].map(getTestLimitOrder); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const fillAmounts = orders.map(order => getRandomPortion(order.takerAmount)); | ||||
|             const tx = await feature | ||||
|                 .batchFillLimitOrders(orders, signatures, fillAmounts, false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Fillable, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: fillAmounts[i], | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map((order, i) => testUtils.createLimitOrderFilledEventArgs(order, fillAmounts[i])), | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders, fillAmounts); | ||||
|         }); | ||||
|         it('Fills multiple orders and refunds excess ETH', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length).plus(420); | ||||
|             const tx = await feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: orders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders); | ||||
|         }); | ||||
|         it('Skips over unfillable orders and refunds excess ETH', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const expiredOrder = getTestLimitOrder({ expiry: createExpiry(-1), takerTokenFeeAmount: ZERO_AMOUNT }); | ||||
|             const orders = [expiredOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const tx = await feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             const [expiredOrderInfo, ...filledOrderInfos] = orderInfos; | ||||
|             assertOrderInfoEquals(expiredOrderInfo, { | ||||
|                 status: OrderStatus.Expired, | ||||
|                 orderHash: expiredOrder.getHash(), | ||||
|                 takerTokenFilledAmount: ZERO_AMOUNT, | ||||
|             }); | ||||
|             filledOrderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: fillableOrders[i].getHash(), | ||||
|                     takerTokenFilledAmount: fillableOrders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 fillableOrders.map(order => testUtils.createLimitOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(fillableOrders); | ||||
|         }); | ||||
|         it('Fills multiple orders with revertIfIncomplete=true', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const tx = await feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: orders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders); | ||||
|         }); | ||||
|         it('If revertIfIncomplete==true, reverts on an unfillable order', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const expiredOrder = getTestLimitOrder({ expiry: createExpiry(-1), takerTokenFeeAmount: ZERO_AMOUNT }); | ||||
|             const orders = [expiredOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const tx = feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.BatchFillIncompleteError( | ||||
|                     expiredOrder.getHash(), | ||||
|                     ZERO_AMOUNT, | ||||
|                     expiredOrder.takerAmount, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|         it('If revertIfIncomplete==true, reverts on an incomplete fill ', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT })); | ||||
|             const partiallyFilledOrder = getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }); | ||||
|             const partialFillAmount = getRandomPortion(partiallyFilledOrder.takerAmount); | ||||
|             await testUtils.fillLimitOrderAsync(partiallyFilledOrder, { fillAmount: partialFillAmount }); | ||||
|             const orders = [partiallyFilledOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const value = testUtils.protocolFee.times(orders.length); | ||||
|             const tx = feature | ||||
|                 .batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.BatchFillIncompleteError( | ||||
|                     partiallyFilledOrder.getHash(), | ||||
|                     partiallyFilledOrder.takerAmount.minus(partialFillAmount), | ||||
|                     partiallyFilledOrder.takerAmount, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|     }); | ||||
|     describe('batchFillRfqOrders', () => { | ||||
|         async function assertExpectedFinalBalancesAsync( | ||||
|             orders: RfqOrder[], | ||||
|             takerTokenFillAmounts: BigNumber[] = orders.map(order => order.takerAmount), | ||||
|             takerTokenAlreadyFilledAmounts: BigNumber[] = orders.map(() => ZERO_AMOUNT), | ||||
|         ): Promise<void> { | ||||
|             const { makerTokenFilledAmount, takerTokenFilledAmount } = orders | ||||
|                 .map((order, i) => | ||||
|                     computeRfqOrderFilledAmounts(order, takerTokenFillAmounts[i], takerTokenAlreadyFilledAmounts[i]), | ||||
|                 ) | ||||
|                 .reduce((previous, current) => ({ | ||||
|                     makerTokenFilledAmount: previous.makerTokenFilledAmount.plus(current.makerTokenFilledAmount), | ||||
|                     takerTokenFilledAmount: previous.takerTokenFilledAmount.plus(current.takerTokenFilledAmount), | ||||
|                 })); | ||||
|             const makerBalance = await takerToken.balanceOf(maker).callAsync(); | ||||
|             const takerBalance = await makerToken.balanceOf(taker).callAsync(); | ||||
|             expect(makerBalance).to.bignumber.eq(takerTokenFilledAmount); | ||||
|             expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount); | ||||
|         } | ||||
|  | ||||
|         it('Fully fills multiple orders', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = await feature | ||||
|                 .batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: orders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map(order => testUtils.createRfqOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders); | ||||
|         }); | ||||
|         it('Partially fills multiple orders', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             const fillAmounts = orders.map(order => getRandomPortion(order.takerAmount)); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = await feature | ||||
|                 .batchFillRfqOrders(orders, signatures, fillAmounts, false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Fillable, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: fillAmounts[i], | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map((order, i) => testUtils.createRfqOrderFilledEventArgs(order, fillAmounts[i])), | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders, fillAmounts); | ||||
|         }); | ||||
|         it('Skips over unfillable orders', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const expiredOrder = getTestRfqOrder({ expiry: createExpiry(-1) }); | ||||
|             const orders = [expiredOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = await feature | ||||
|                 .batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             const [expiredOrderInfo, ...filledOrderInfos] = orderInfos; | ||||
|             assertOrderInfoEquals(expiredOrderInfo, { | ||||
|                 status: OrderStatus.Expired, | ||||
|                 orderHash: expiredOrder.getHash(), | ||||
|                 takerTokenFilledAmount: ZERO_AMOUNT, | ||||
|             }); | ||||
|             filledOrderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: fillableOrders[i].getHash(), | ||||
|                     takerTokenFilledAmount: fillableOrders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 fillableOrders.map(order => testUtils.createRfqOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(fillableOrders); | ||||
|         }); | ||||
|         it('Fills multiple orders with revertIfIncomplete=true', async () => { | ||||
|             const orders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = await feature | ||||
|                 .batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync(); | ||||
|             orderInfos.map((orderInfo, i) => | ||||
|                 assertOrderInfoEquals(orderInfo, { | ||||
|                     status: OrderStatus.Filled, | ||||
|                     orderHash: orders[i].getHash(), | ||||
|                     takerTokenFilledAmount: orders[i].takerAmount, | ||||
|                 }), | ||||
|             ); | ||||
|             verifyEventsFromLogs( | ||||
|                 tx.logs, | ||||
|                 orders.map(order => testUtils.createRfqOrderFilledEventArgs(order)), | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             return assertExpectedFinalBalancesAsync(orders); | ||||
|         }); | ||||
|         it('If revertIfIncomplete==true, reverts on an unfillable order', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const expiredOrder = getTestRfqOrder({ expiry: createExpiry(-1) }); | ||||
|             const orders = [expiredOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = feature | ||||
|                 .batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.BatchFillIncompleteError( | ||||
|                     expiredOrder.getHash(), | ||||
|                     ZERO_AMOUNT, | ||||
|                     expiredOrder.takerAmount, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|         it('If revertIfIncomplete==true, reverts on an incomplete fill ', async () => { | ||||
|             const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder()); | ||||
|             const partiallyFilledOrder = getTestRfqOrder(); | ||||
|             const partialFillAmount = getRandomPortion(partiallyFilledOrder.takerAmount); | ||||
|             await testUtils.fillRfqOrderAsync(partiallyFilledOrder, partialFillAmount); | ||||
|             const orders = [partiallyFilledOrder, ...fillableOrders]; | ||||
|             const signatures = await Promise.all( | ||||
|                 orders.map(order => order.getSignatureWithProviderAsync(env.provider)), | ||||
|             ); | ||||
|             await testUtils.prepareBalancesForOrdersAsync(orders); | ||||
|             const tx = feature | ||||
|                 .batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.BatchFillIncompleteError( | ||||
|                     partiallyFilledOrder.getHash(), | ||||
|                     partiallyFilledOrder.takerAmount.minus(partialFillAmount), | ||||
|                     partiallyFilledOrder.takerAmount, | ||||
|                 ), | ||||
|             ); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										764
									
								
								contracts/zero-ex/test/features/multiplex_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										764
									
								
								contracts/zero-ex/test/features/multiplex_test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,764 @@ | ||||
| import { | ||||
|     artifacts as erc20Artifacts, | ||||
|     ERC20TokenContract, | ||||
|     WETH9Contract, | ||||
|     WETH9DepositEventArgs, | ||||
|     WETH9Events, | ||||
|     WETH9WithdrawalEventArgs, | ||||
| } from '@0x/contracts-erc20'; | ||||
| import { blockchainTests, constants, expect, filterLogsToArguments, toBaseUnitAmount } from '@0x/contracts-test-utils'; | ||||
| import { | ||||
|     BridgeSource, | ||||
|     encodeFillQuoteTransformerData, | ||||
|     encodePayTakerTransformerData, | ||||
|     FillQuoteTransformerOrderType, | ||||
|     FillQuoteTransformerSide, | ||||
|     findTransformerNonce, | ||||
|     RfqOrder, | ||||
|     SIGNATURE_ABI, | ||||
| } from '@0x/protocol-utils'; | ||||
| import { AbiEncoder, BigNumber, logUtils } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { abis } from '../utils/abis'; | ||||
| import { getRandomRfqOrder } from '../utils/orders'; | ||||
| import { | ||||
|     BridgeAdapterBridgeFillEventArgs, | ||||
|     BridgeAdapterEvents, | ||||
|     IUniswapV2PairEvents, | ||||
|     IUniswapV2PairSwapEventArgs, | ||||
|     IZeroExContract, | ||||
|     IZeroExEvents, | ||||
|     IZeroExRfqOrderFilledEventArgs, | ||||
|     MultiplexFeatureContract, | ||||
|     MultiplexFeatureEvents, | ||||
|     MultiplexFeatureLiquidityProviderSwapEventArgs, | ||||
|     SimpleFunctionRegistryFeatureContract, | ||||
| } from '../wrappers'; | ||||
|  | ||||
| const HIGH_BIT = new BigNumber(2).pow(255); | ||||
| function encodeFractionalFillAmount(frac: number): BigNumber { | ||||
|     return HIGH_BIT.plus(new BigNumber(frac).times('1e18').integerValue()); | ||||
| } | ||||
|  | ||||
| const EP_GOVERNOR = '0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e'; | ||||
| const DAI_WALLET = '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8'; | ||||
| const WETH_WALLET = '0x1e0447b19bb6ecfdae1e4ae1694b0c3659614e4e'; | ||||
| const USDC_WALLET = '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8'; | ||||
| blockchainTests.configure({ | ||||
|     fork: { | ||||
|         unlockedAccounts: [EP_GOVERNOR, DAI_WALLET, WETH_WALLET, USDC_WALLET], | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| interface WrappedBatchCall { | ||||
|     selector: string; | ||||
|     sellAmount: BigNumber; | ||||
|     data: string; | ||||
| } | ||||
|  | ||||
| blockchainTests.fork.skip('Multiplex feature', env => { | ||||
|     const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f'; | ||||
|     const dai = new ERC20TokenContract(DAI_ADDRESS, env.provider, env.txDefaults); | ||||
|     const ETH_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; | ||||
|     const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; | ||||
|     const weth = new WETH9Contract(WETH_ADDRESS, env.provider, env.txDefaults); | ||||
|     const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; | ||||
|     const USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7'; | ||||
|     const usdt = new ERC20TokenContract(USDT_ADDRESS, env.provider, env.txDefaults); | ||||
|     const LON_ADDRESS = '0x0000000000095413afc295d19edeb1ad7b71c952'; | ||||
|     const PLP_SANDBOX_ADDRESS = '0x407b4128e9ecad8769b2332312a9f655cb9f5f3a'; | ||||
|     const WETH_DAI_PLP_ADDRESS = '0x1db681925786441ba82adefac7bf492089665ca0'; | ||||
|     const WETH_USDC_PLP_ADDRESS = '0x8463c03c0c57ff19fa8b431e0d3a34e2df89888e'; | ||||
|     const USDC_USDT_PLP_ADDRESS = '0xc340ef96449514cea4dfa11d847a06d7f03d437c'; | ||||
|     const GREEDY_TOKENS_BLOOM_FILTER = '0x0000100800000480002c00401000000820000000000000020000001010800001'; | ||||
|     const BALANCER_WETH_DAI = '0x8b6e6e7b5b3801fed2cafd4b22b8a16c2f2db21a'; | ||||
|     const fqtNonce = findTransformerNonce( | ||||
|         '0xfa6282736af206cb4cfc5cb786d82aecdf1186f9', | ||||
|         '0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb', | ||||
|     ); | ||||
|     const payTakerNonce = findTransformerNonce( | ||||
|         '0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e', | ||||
|         '0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb', | ||||
|     ); | ||||
|  | ||||
|     let zeroEx: IZeroExContract; | ||||
|     let multiplex: MultiplexFeatureContract; | ||||
|     let rfqMaker: string; | ||||
|     let flashWalletAddress: string; | ||||
|  | ||||
|     before(async () => { | ||||
|         const erc20Abis = _.mapValues(erc20Artifacts, v => v.compilerOutput.abi); | ||||
|         [rfqMaker] = await env.getAccountAddressesAsync(); | ||||
|         zeroEx = new IZeroExContract('0xdef1c0ded9bec7f1a1670819833240f027b25eff', env.provider, env.txDefaults, { | ||||
|             ...abis, | ||||
|             ...erc20Abis, | ||||
|         }); | ||||
|         flashWalletAddress = await zeroEx.getTransformWallet().callAsync(); | ||||
|         const registry = new SimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults, { | ||||
|             ...abis, | ||||
|             ...erc20Abis, | ||||
|         }); | ||||
|         multiplex = new MultiplexFeatureContract(zeroEx.address, env.provider, env.txDefaults, { | ||||
|             ...abis, | ||||
|             ...erc20Abis, | ||||
|         }); | ||||
|         const multiplexImpl = await MultiplexFeatureContract.deployFrom0xArtifactAsync( | ||||
|             artifacts.MultiplexFeature, | ||||
|             env.provider, | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|             zeroEx.address, | ||||
|             WETH_ADDRESS, | ||||
|             PLP_SANDBOX_ADDRESS, | ||||
|             GREEDY_TOKENS_BLOOM_FILTER, | ||||
|         ); | ||||
|         await registry | ||||
|             .extend(multiplex.getSelector('batchFill'), multiplexImpl.address) | ||||
|             .awaitTransactionSuccessAsync({ from: EP_GOVERNOR, gasPrice: 0 }, { shouldValidate: false }); | ||||
|         await registry | ||||
|             .extend(multiplex.getSelector('multiHopFill'), multiplexImpl.address) | ||||
|             .awaitTransactionSuccessAsync({ from: EP_GOVERNOR, gasPrice: 0 }, { shouldValidate: false }); | ||||
|         await dai | ||||
|             .approve(zeroEx.address, constants.MAX_UINT256) | ||||
|             .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|         await weth | ||||
|             .transfer(rfqMaker, toBaseUnitAmount(100)) | ||||
|             .awaitTransactionSuccessAsync({ from: WETH_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|         await weth | ||||
|             .approve(zeroEx.address, constants.MAX_UINT256) | ||||
|             .awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false }); | ||||
|     }); | ||||
|     describe('batchFill', () => { | ||||
|         let rfqDataEncoder: AbiEncoder.DataType; | ||||
|         let uniswapCall: WrappedBatchCall; | ||||
|         let sushiswapCall: WrappedBatchCall; | ||||
|         let plpCall: WrappedBatchCall; | ||||
|         let rfqCall: WrappedBatchCall; | ||||
|         let rfqOrder: RfqOrder; | ||||
|         before(async () => { | ||||
|             rfqDataEncoder = AbiEncoder.create([ | ||||
|                 { name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI }, | ||||
|                 { name: 'signature', type: 'tuple', components: SIGNATURE_ABI }, | ||||
|             ]); | ||||
|             rfqOrder = getRandomRfqOrder({ | ||||
|                 maker: rfqMaker, | ||||
|                 verifyingContract: zeroEx.address, | ||||
|                 chainId: 1, | ||||
|                 takerToken: DAI_ADDRESS, | ||||
|                 makerToken: WETH_ADDRESS, | ||||
|                 makerAmount: toBaseUnitAmount(100), | ||||
|                 takerAmount: toBaseUnitAmount(100), | ||||
|                 txOrigin: DAI_WALLET, | ||||
|             }); | ||||
|             rfqCall = { | ||||
|                 selector: zeroEx.getSelector('_fillRfqOrder'), | ||||
|                 sellAmount: toBaseUnitAmount(1), | ||||
|                 data: rfqDataEncoder.encode({ | ||||
|                     order: rfqOrder, | ||||
|                     signature: await rfqOrder.getSignatureWithProviderAsync(env.provider), | ||||
|                 }), | ||||
|             }; | ||||
|             const uniswapDataEncoder = AbiEncoder.create([ | ||||
|                 { name: 'tokens', type: 'address[]' }, | ||||
|                 { name: 'isSushi', type: 'bool' }, | ||||
|             ]); | ||||
|             const plpDataEncoder = AbiEncoder.create([ | ||||
|                 { name: 'provider', type: 'address' }, | ||||
|                 { name: 'auxiliaryData', type: 'bytes' }, | ||||
|             ]); | ||||
|             uniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: toBaseUnitAmount(1.01), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [DAI_ADDRESS, WETH_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|             sushiswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: toBaseUnitAmount(1.02), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [DAI_ADDRESS, WETH_ADDRESS], isSushi: true }), | ||||
|             }; | ||||
|             plpCall = { | ||||
|                 selector: multiplex.getSelector('_sellToLiquidityProvider'), | ||||
|                 sellAmount: toBaseUnitAmount(1.03), | ||||
|                 data: plpDataEncoder.encode({ | ||||
|                     provider: WETH_DAI_PLP_ADDRESS, | ||||
|                     auxiliaryData: constants.NULL_BYTES, | ||||
|                 }), | ||||
|             }; | ||||
|         }); | ||||
|         it('MultiplexFeature.batchFill(RFQ, unused Uniswap fallback)', async () => { | ||||
|             const batchFillData = { | ||||
|                 inputToken: DAI_ADDRESS, | ||||
|                 outputToken: WETH_ADDRESS, | ||||
|                 sellAmount: rfqCall.sellAmount, | ||||
|                 calls: [rfqCall, uniswapCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .batchFill(batchFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|  | ||||
|             const [rfqEvent] = filterLogsToArguments<IZeroExRfqOrderFilledEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             expect(rfqEvent.maker).to.equal(rfqMaker); | ||||
|             expect(rfqEvent.taker).to.equal(DAI_WALLET); | ||||
|             expect(rfqEvent.makerToken).to.equal(WETH_ADDRESS); | ||||
|             expect(rfqEvent.takerToken).to.equal(DAI_ADDRESS); | ||||
|             expect(rfqEvent.takerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount); | ||||
|             expect(rfqEvent.makerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount); | ||||
|         }); | ||||
|         it('MultiplexFeature.batchFill(expired RFQ, Uniswap fallback)', async () => { | ||||
|             const expiredRfqOrder = getRandomRfqOrder({ | ||||
|                 maker: rfqMaker, | ||||
|                 verifyingContract: zeroEx.address, | ||||
|                 chainId: 1, | ||||
|                 takerToken: DAI_ADDRESS, | ||||
|                 makerToken: WETH_ADDRESS, | ||||
|                 makerAmount: toBaseUnitAmount(100), | ||||
|                 takerAmount: toBaseUnitAmount(100), | ||||
|                 txOrigin: DAI_WALLET, | ||||
|                 expiry: new BigNumber(0), | ||||
|             }); | ||||
|             const expiredRfqCall = { | ||||
|                 selector: zeroEx.getSelector('_fillRfqOrder'), | ||||
|                 sellAmount: toBaseUnitAmount(1.23), | ||||
|                 data: rfqDataEncoder.encode({ | ||||
|                     order: expiredRfqOrder, | ||||
|                     signature: await expiredRfqOrder.getSignatureWithProviderAsync(env.provider), | ||||
|                 }), | ||||
|             }; | ||||
|  | ||||
|             const batchFillData = { | ||||
|                 inputToken: DAI_ADDRESS, | ||||
|                 outputToken: WETH_ADDRESS, | ||||
|                 sellAmount: expiredRfqCall.sellAmount, | ||||
|                 calls: [expiredRfqCall, uniswapCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .batchFill(batchFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|             const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IUniswapV2PairEvents.Swap, | ||||
|             ); | ||||
|             expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address); | ||||
|             expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(DAI_WALLET); | ||||
|             expect( | ||||
|                 BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In), | ||||
|                 'Uniswap Swap event input amount', | ||||
|             ).to.bignumber.equal(uniswapCall.sellAmount); | ||||
|             expect( | ||||
|                 BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out), | ||||
|                 'Uniswap Swap event output amount', | ||||
|             ).to.bignumber.gt(0); | ||||
|         }); | ||||
|         it('MultiplexFeature.batchFill(expired RFQ, Balancer FQT fallback)', async () => { | ||||
|             const expiredRfqOrder = getRandomRfqOrder({ | ||||
|                 maker: rfqMaker, | ||||
|                 verifyingContract: zeroEx.address, | ||||
|                 chainId: 1, | ||||
|                 takerToken: DAI_ADDRESS, | ||||
|                 makerToken: WETH_ADDRESS, | ||||
|                 makerAmount: toBaseUnitAmount(100), | ||||
|                 takerAmount: toBaseUnitAmount(100), | ||||
|                 txOrigin: DAI_WALLET, | ||||
|                 expiry: new BigNumber(0), | ||||
|             }); | ||||
|             const expiredRfqCall = { | ||||
|                 selector: zeroEx.getSelector('_fillRfqOrder'), | ||||
|                 sellAmount: toBaseUnitAmount(1.23), | ||||
|                 data: rfqDataEncoder.encode({ | ||||
|                     order: expiredRfqOrder, | ||||
|                     signature: await expiredRfqOrder.getSignatureWithProviderAsync(env.provider), | ||||
|                 }), | ||||
|             }; | ||||
|             const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]); | ||||
|             const fqtData = encodeFillQuoteTransformerData({ | ||||
|                 side: FillQuoteTransformerSide.Sell, | ||||
|                 sellToken: DAI_ADDRESS, | ||||
|                 buyToken: WETH_ADDRESS, | ||||
|                 bridgeOrders: [ | ||||
|                     { | ||||
|                         source: BridgeSource.Balancer, | ||||
|                         takerTokenAmount: expiredRfqCall.sellAmount, | ||||
|                         makerTokenAmount: expiredRfqCall.sellAmount, | ||||
|                         bridgeData: poolEncoder.encode([BALANCER_WETH_DAI]), | ||||
|                     }, | ||||
|                 ], | ||||
|                 limitOrders: [], | ||||
|                 rfqOrders: [], | ||||
|                 fillSequence: [FillQuoteTransformerOrderType.Bridge], | ||||
|                 fillAmount: expiredRfqCall.sellAmount, | ||||
|                 refundReceiver: constants.NULL_ADDRESS, | ||||
|             }); | ||||
|             const payTakerData = encodePayTakerTransformerData({ | ||||
|                 tokens: [WETH_ADDRESS], | ||||
|                 amounts: [constants.MAX_UINT256], | ||||
|             }); | ||||
|             const transformERC20Encoder = AbiEncoder.create([ | ||||
|                 { | ||||
|                     name: 'transformations', | ||||
|                     type: 'tuple[]', | ||||
|                     components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }], | ||||
|                 }, | ||||
|                 { name: 'ethValue', type: 'uint256' }, | ||||
|             ]); | ||||
|             const balancerFqtCall = { | ||||
|                 selector: zeroEx.getSelector('_transformERC20'), | ||||
|                 sellAmount: expiredRfqCall.sellAmount, | ||||
|                 data: transformERC20Encoder.encode({ | ||||
|                     transformations: [ | ||||
|                         { | ||||
|                             deploymentNonce: fqtNonce, | ||||
|                             data: fqtData, | ||||
|                         }, | ||||
|                         { | ||||
|                             deploymentNonce: payTakerNonce, | ||||
|                             data: payTakerData, | ||||
|                         }, | ||||
|                     ], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|  | ||||
|             const batchFillData = { | ||||
|                 inputToken: DAI_ADDRESS, | ||||
|                 outputToken: WETH_ADDRESS, | ||||
|                 sellAmount: expiredRfqCall.sellAmount, | ||||
|                 calls: [expiredRfqCall, balancerFqtCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .batchFill(batchFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|             const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>( | ||||
|                 tx.logs, | ||||
|                 BridgeAdapterEvents.BridgeFill, | ||||
|             ); | ||||
|             expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Balancer); | ||||
|             expect(bridgeFillEvent.inputToken).to.equal(DAI_ADDRESS); | ||||
|             expect(bridgeFillEvent.outputToken).to.equal(WETH_ADDRESS); | ||||
|             expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(expiredRfqCall.sellAmount); | ||||
|             expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0); | ||||
|         }); | ||||
|         it('MultiplexFeature.batchFill(Sushiswap, PLP, Uniswap, RFQ)', async () => { | ||||
|             const batchFillData = { | ||||
|                 inputToken: DAI_ADDRESS, | ||||
|                 outputToken: WETH_ADDRESS, | ||||
|                 sellAmount: BigNumber.sum( | ||||
|                     sushiswapCall.sellAmount, | ||||
|                     plpCall.sellAmount, | ||||
|                     uniswapCall.sellAmount, | ||||
|                     rfqCall.sellAmount, | ||||
|                 ), | ||||
|                 calls: [sushiswapCall, plpCall, uniswapCall, rfqCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .batchFill(batchFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|             const [sushiswapEvent, uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IUniswapV2PairEvents.Swap, | ||||
|             ); | ||||
|             expect(sushiswapEvent.sender, 'Sushiswap Swap event sender').to.equal(zeroEx.address); | ||||
|             expect(sushiswapEvent.to, 'Sushiswap Swap event to').to.equal(DAI_WALLET); | ||||
|             expect( | ||||
|                 BigNumber.max(sushiswapEvent.amount0In, sushiswapEvent.amount1In), | ||||
|                 'Sushiswap Swap event input amount', | ||||
|             ).to.bignumber.equal(sushiswapCall.sellAmount); | ||||
|             expect( | ||||
|                 BigNumber.max(sushiswapEvent.amount0Out, sushiswapEvent.amount1Out), | ||||
|                 'Sushiswap Swap event output amount', | ||||
|             ).to.bignumber.gt(0); | ||||
|             expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address); | ||||
|             expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(DAI_WALLET); | ||||
|             expect( | ||||
|                 BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In), | ||||
|                 'Uniswap Swap event input amount', | ||||
|             ).to.bignumber.equal(uniswapCall.sellAmount); | ||||
|             expect( | ||||
|                 BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out), | ||||
|                 'Uniswap Swap event output amount', | ||||
|             ).to.bignumber.gt(0); | ||||
|  | ||||
|             const [plpEvent] = filterLogsToArguments<MultiplexFeatureLiquidityProviderSwapEventArgs>( | ||||
|                 tx.logs, | ||||
|                 MultiplexFeatureEvents.LiquidityProviderSwap, | ||||
|             ); | ||||
|             expect(plpEvent.inputToken, 'LiquidityProviderSwap event inputToken').to.equal(batchFillData.inputToken); | ||||
|             expect(plpEvent.outputToken, 'LiquidityProviderSwap event outputToken').to.equal(batchFillData.outputToken); | ||||
|             expect(plpEvent.inputTokenAmount, 'LiquidityProviderSwap event inputToken').to.bignumber.equal( | ||||
|                 plpCall.sellAmount, | ||||
|             ); | ||||
|             expect(plpEvent.outputTokenAmount, 'LiquidityProviderSwap event outputTokenAmount').to.bignumber.gt(0); | ||||
|             expect(plpEvent.provider, 'LiquidityProviderSwap event provider address').to.equal(WETH_DAI_PLP_ADDRESS); | ||||
|             expect(plpEvent.recipient, 'LiquidityProviderSwap event recipient address').to.equal(DAI_WALLET); | ||||
|  | ||||
|             const [rfqEvent] = filterLogsToArguments<IZeroExRfqOrderFilledEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             expect(rfqEvent.maker).to.equal(rfqMaker); | ||||
|             expect(rfqEvent.taker).to.equal(DAI_WALLET); | ||||
|             expect(rfqEvent.makerToken).to.equal(WETH_ADDRESS); | ||||
|             expect(rfqEvent.takerToken).to.equal(DAI_ADDRESS); | ||||
|             expect(rfqEvent.takerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount); | ||||
|             expect(rfqEvent.makerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount); | ||||
|         }); | ||||
|     }); | ||||
|     describe('multiHopFill', () => { | ||||
|         let uniswapDataEncoder: AbiEncoder.DataType; | ||||
|         let plpDataEncoder: AbiEncoder.DataType; | ||||
|         let curveEncoder: AbiEncoder.DataType; | ||||
|         let transformERC20Encoder: AbiEncoder.DataType; | ||||
|         let batchFillEncoder: AbiEncoder.DataType; | ||||
|         let multiHopFillEncoder: AbiEncoder.DataType; | ||||
|  | ||||
|         before(async () => { | ||||
|             uniswapDataEncoder = AbiEncoder.create([ | ||||
|                 { name: 'tokens', type: 'address[]' }, | ||||
|                 { name: 'isSushi', type: 'bool' }, | ||||
|             ]); | ||||
|             plpDataEncoder = AbiEncoder.create([ | ||||
|                 { name: 'provider', type: 'address' }, | ||||
|                 { name: 'auxiliaryData', type: 'bytes' }, | ||||
|             ]); | ||||
|             curveEncoder = AbiEncoder.create([ | ||||
|                 { name: 'curveAddress', type: 'address' }, | ||||
|                 { name: 'exchangeFunctionSelector', type: 'bytes4' }, | ||||
|                 { name: 'fromTokenIdx', type: 'int128' }, | ||||
|                 { name: 'toTokenIdx', type: 'int128' }, | ||||
|             ]); | ||||
|             transformERC20Encoder = AbiEncoder.create([ | ||||
|                 { | ||||
|                     name: 'transformations', | ||||
|                     type: 'tuple[]', | ||||
|                     components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }], | ||||
|                 }, | ||||
|                 { name: 'ethValue', type: 'uint256' }, | ||||
|             ]); | ||||
|             batchFillEncoder = AbiEncoder.create([ | ||||
|                 { | ||||
|                     name: 'calls', | ||||
|                     type: 'tuple[]', | ||||
|                     components: [ | ||||
|                         { name: 'selector', type: 'bytes4' }, | ||||
|                         { name: 'sellAmount', type: 'uint256' }, | ||||
|                         { name: 'data', type: 'bytes' }, | ||||
|                     ], | ||||
|                 }, | ||||
|                 { name: 'ethValue', type: 'uint256' }, | ||||
|             ]); | ||||
|             multiHopFillEncoder = AbiEncoder.create([ | ||||
|                 { name: 'tokens', type: 'address[]' }, | ||||
|                 { | ||||
|                     name: 'calls', | ||||
|                     type: 'tuple[]', | ||||
|                     components: [{ name: 'selector', type: 'bytes4' }, { name: 'data', type: 'bytes' }], | ||||
|                 }, | ||||
|                 { name: 'ethValue', type: 'uint256' }, | ||||
|             ]); | ||||
|         }); | ||||
|         it('MultiplexFeature.multiHopFill(DAI ––Curve––> USDC ––Uni––> WETH ––unwrap––> ETH)', async () => { | ||||
|             const sellAmount = toBaseUnitAmount(1000000); // 1M DAI | ||||
|             const fqtData = encodeFillQuoteTransformerData({ | ||||
|                 side: FillQuoteTransformerSide.Sell, | ||||
|                 sellToken: DAI_ADDRESS, | ||||
|                 buyToken: USDC_ADDRESS, | ||||
|                 bridgeOrders: [ | ||||
|                     { | ||||
|                         source: BridgeSource.Curve, | ||||
|                         takerTokenAmount: sellAmount, | ||||
|                         makerTokenAmount: sellAmount, | ||||
|                         bridgeData: curveEncoder.encode([ | ||||
|                             '0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7', // 3-pool | ||||
|                             '0x3df02124', // `exchange` selector | ||||
|                             0, // DAI | ||||
|                             1, // USDC | ||||
|                         ]), | ||||
|                     }, | ||||
|                 ], | ||||
|                 limitOrders: [], | ||||
|                 rfqOrders: [], | ||||
|                 fillSequence: [FillQuoteTransformerOrderType.Bridge], | ||||
|                 fillAmount: sellAmount, | ||||
|                 refundReceiver: constants.NULL_ADDRESS, | ||||
|             }); | ||||
|             const payTakerData = encodePayTakerTransformerData({ | ||||
|                 tokens: [USDC_ADDRESS], | ||||
|                 amounts: [constants.MAX_UINT256], | ||||
|             }); | ||||
|             const curveFqtCall = { | ||||
|                 selector: zeroEx.getSelector('_transformERC20'), | ||||
|                 sellAmount, | ||||
|                 data: transformERC20Encoder.encode({ | ||||
|                     transformations: [ | ||||
|                         { | ||||
|                             deploymentNonce: fqtNonce, | ||||
|                             data: fqtData, | ||||
|                         }, | ||||
|                         { | ||||
|                             deploymentNonce: payTakerNonce, | ||||
|                             data: payTakerData, | ||||
|                         }, | ||||
|                     ], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const uniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [USDC_ADDRESS, WETH_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|             const unwrapEthCall = { | ||||
|                 selector: weth.getSelector('withdraw'), | ||||
|                 data: constants.NULL_BYTES, | ||||
|             }; | ||||
|             const multiHopFillData = { | ||||
|                 tokens: [DAI_ADDRESS, USDC_ADDRESS, WETH_ADDRESS, ETH_TOKEN_ADDRESS], | ||||
|                 sellAmount, | ||||
|                 calls: [curveFqtCall, uniswapCall, unwrapEthCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .multiHopFill(multiHopFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|             const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>( | ||||
|                 tx.logs, | ||||
|                 BridgeAdapterEvents.BridgeFill, | ||||
|             ); | ||||
|             expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Curve); | ||||
|             expect(bridgeFillEvent.inputToken).to.equal(DAI_ADDRESS); | ||||
|             expect(bridgeFillEvent.outputToken).to.equal(USDC_ADDRESS); | ||||
|             expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(sellAmount); | ||||
|             expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0); | ||||
|             const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IUniswapV2PairEvents.Swap, | ||||
|             ); | ||||
|             expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address); | ||||
|             expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(zeroEx.address); | ||||
|             const uniswapInputAmount = BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In); | ||||
|             expect(uniswapInputAmount, 'Uniswap Swap event input amount').to.bignumber.equal( | ||||
|                 bridgeFillEvent.outputTokenAmount, | ||||
|             ); | ||||
|             const uniswapOutputAmount = BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out); | ||||
|             expect(uniswapOutputAmount, 'Uniswap Swap event output amount').to.bignumber.gt(0); | ||||
|             const [wethWithdrawalEvent] = filterLogsToArguments<WETH9WithdrawalEventArgs>( | ||||
|                 tx.logs, | ||||
|                 WETH9Events.Withdrawal, | ||||
|             ); | ||||
|             expect(wethWithdrawalEvent._owner, 'WETH Withdrawal event _owner').to.equal(zeroEx.address); | ||||
|             expect(wethWithdrawalEvent._value, 'WETH Withdrawal event _value').to.bignumber.equal(uniswapOutputAmount); | ||||
|         }); | ||||
|         it('MultiplexFeature.multiHopFill(ETH ––wrap–-> WETH ––Uni––> USDC ––Curve––> DAI)', async () => { | ||||
|             const sellAmount = toBaseUnitAmount(1); // 1 ETH | ||||
|             const fqtData = encodeFillQuoteTransformerData({ | ||||
|                 side: FillQuoteTransformerSide.Sell, | ||||
|                 sellToken: USDC_ADDRESS, | ||||
|                 buyToken: DAI_ADDRESS, | ||||
|                 bridgeOrders: [ | ||||
|                     { | ||||
|                         source: BridgeSource.Curve, | ||||
|                         takerTokenAmount: constants.MAX_UINT256, | ||||
|                         makerTokenAmount: constants.MAX_UINT256, | ||||
|                         bridgeData: curveEncoder.encode([ | ||||
|                             '0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7', // 3-pool | ||||
|                             '0x3df02124', // `exchange` selector | ||||
|                             1, // USDC | ||||
|                             0, // DAI | ||||
|                         ]), | ||||
|                     }, | ||||
|                 ], | ||||
|                 limitOrders: [], | ||||
|                 rfqOrders: [], | ||||
|                 fillSequence: [FillQuoteTransformerOrderType.Bridge], | ||||
|                 fillAmount: constants.MAX_UINT256, | ||||
|                 refundReceiver: constants.NULL_ADDRESS, | ||||
|             }); | ||||
|             const payTakerData = encodePayTakerTransformerData({ | ||||
|                 tokens: [DAI_ADDRESS], | ||||
|                 amounts: [constants.MAX_UINT256], | ||||
|             }); | ||||
|             const curveFqtCall = { | ||||
|                 selector: zeroEx.getSelector('_transformERC20'), | ||||
|                 data: transformERC20Encoder.encode({ | ||||
|                     transformations: [ | ||||
|                         { | ||||
|                             deploymentNonce: fqtNonce, | ||||
|                             data: fqtData, | ||||
|                         }, | ||||
|                         { | ||||
|                             deploymentNonce: payTakerNonce, | ||||
|                             data: payTakerData, | ||||
|                         }, | ||||
|                     ], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const uniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDC_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|             const wrapEthCall = { | ||||
|                 selector: weth.getSelector('deposit'), | ||||
|                 data: constants.NULL_BYTES, | ||||
|             }; | ||||
|             const multiHopFillData = { | ||||
|                 tokens: [ETH_TOKEN_ADDRESS, WETH_ADDRESS, USDC_ADDRESS, DAI_ADDRESS], | ||||
|                 sellAmount, | ||||
|                 calls: [wrapEthCall, uniswapCall, curveFqtCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .multiHopFill(multiHopFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync( | ||||
|                     { from: rfqMaker, gasPrice: 0, value: sellAmount }, | ||||
|                     { shouldValidate: false }, | ||||
|                 ); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|  | ||||
|             const [wethDepositEvent] = filterLogsToArguments<WETH9DepositEventArgs>(tx.logs, WETH9Events.Deposit); | ||||
|             expect(wethDepositEvent._owner, 'WETH Deposit event _owner').to.equal(zeroEx.address); | ||||
|             expect(wethDepositEvent._value, 'WETH Deposit event _value').to.bignumber.equal(sellAmount); | ||||
|  | ||||
|             const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>( | ||||
|                 tx.logs, | ||||
|                 IUniswapV2PairEvents.Swap, | ||||
|             ); | ||||
|             expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address); | ||||
|             expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(flashWalletAddress); | ||||
|             const uniswapInputAmount = BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In); | ||||
|             expect(uniswapInputAmount, 'Uniswap Swap event input amount').to.bignumber.equal(sellAmount); | ||||
|             const uniswapOutputAmount = BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out); | ||||
|             expect(uniswapOutputAmount, 'Uniswap Swap event output amount').to.bignumber.gt(0); | ||||
|  | ||||
|             const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>( | ||||
|                 tx.logs, | ||||
|                 BridgeAdapterEvents.BridgeFill, | ||||
|             ); | ||||
|             expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Curve); | ||||
|             expect(bridgeFillEvent.inputToken).to.equal(USDC_ADDRESS); | ||||
|             expect(bridgeFillEvent.outputToken).to.equal(DAI_ADDRESS); | ||||
|             expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(uniswapOutputAmount); | ||||
|             expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0); | ||||
|         }); | ||||
|         it.skip('MultiplexFeature.multiHopFill() complex scenario', async () => { | ||||
|             /* | ||||
|  | ||||
|                                       /––––PLP–––> USDC | ||||
|                                      /                \ | ||||
|                                     /                 PLP | ||||
|                                    /––Uni (via USDC)–––\ | ||||
|                                   /                    V | ||||
|                 ETH ––wrap––> WETH ––––Uni/Sushi–––> USDT ––Sushi––> LON | ||||
|                                 \                                      ^ | ||||
|                                  ––––––––––––––– Uni –––––––––––––––––/ | ||||
|             */ | ||||
|             // Taker has to have approved the EP for the intermediate tokens :/ | ||||
|             await weth | ||||
|                 .approve(zeroEx.address, constants.MAX_UINT256) | ||||
|                 .awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false }); | ||||
|             await usdt | ||||
|                 .approve(zeroEx.address, constants.MAX_UINT256) | ||||
|                 .awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false }); | ||||
|  | ||||
|             const sellAmount = toBaseUnitAmount(1); // 1 ETH | ||||
|             const wethUsdcPlpCall = { | ||||
|                 selector: multiplex.getSelector('_sellToLiquidityProvider'), | ||||
|                 data: plpDataEncoder.encode({ | ||||
|                     provider: WETH_USDC_PLP_ADDRESS, | ||||
|                     auxiliaryData: constants.NULL_BYTES, | ||||
|                 }), | ||||
|             }; | ||||
|             const usdcUsdtPlpCall = { | ||||
|                 selector: multiplex.getSelector('_sellToLiquidityProvider'), | ||||
|                 data: plpDataEncoder.encode({ | ||||
|                     provider: USDC_USDT_PLP_ADDRESS, | ||||
|                     auxiliaryData: constants.NULL_BYTES, | ||||
|                 }), | ||||
|             }; | ||||
|             const wethUsdcUsdtMultiHopCall = { | ||||
|                 selector: multiplex.getSelector('_multiHopFill'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.25), | ||||
|                 data: multiHopFillEncoder.encode({ | ||||
|                     tokens: [WETH_ADDRESS, USDC_ADDRESS, USDT_ADDRESS], | ||||
|                     calls: [wethUsdcPlpCall, usdcUsdtPlpCall], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const wethUsdcUsdtUniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.25), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDC_ADDRESS, USDT_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|             const wethUsdtUniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.25), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDT_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|             const wethUsdtSushiswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.25), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDT_ADDRESS], isSushi: true }), | ||||
|             }; | ||||
|             const wethUsdtBatchCall = { | ||||
|                 selector: multiplex.getSelector('_batchFill'), | ||||
|                 data: batchFillEncoder.encode({ | ||||
|                     calls: [ | ||||
|                         wethUsdcUsdtMultiHopCall, | ||||
|                         wethUsdcUsdtUniswapCall, | ||||
|                         wethUsdtUniswapCall, | ||||
|                         wethUsdtSushiswapCall, | ||||
|                     ], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const usdtLonSushiCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [USDT_ADDRESS, LON_ADDRESS], isSushi: true }), | ||||
|             }; | ||||
|             const wethUsdtLonMultiHopCall = { | ||||
|                 selector: multiplex.getSelector('_multiHopFill'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.8), | ||||
|                 data: multiHopFillEncoder.encode({ | ||||
|                     tokens: [WETH_ADDRESS, USDT_ADDRESS], | ||||
|                     calls: [wethUsdtBatchCall, usdtLonSushiCall], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const wethLonUniswapCall = { | ||||
|                 selector: multiplex.getSelector('_sellToUniswap'), | ||||
|                 sellAmount: encodeFractionalFillAmount(0.2), | ||||
|                 data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, LON_ADDRESS], isSushi: false }), | ||||
|             }; | ||||
|  | ||||
|             const wethLonBatchFillCall = { | ||||
|                 selector: multiplex.getSelector('_batchFill'), | ||||
|                 data: batchFillEncoder.encode({ | ||||
|                     calls: [wethUsdtLonMultiHopCall, wethLonUniswapCall], | ||||
|                     ethValue: constants.ZERO_AMOUNT, | ||||
|                 }), | ||||
|             }; | ||||
|             const wrapEthCall = { | ||||
|                 selector: weth.getSelector('deposit'), | ||||
|                 data: constants.NULL_BYTES, | ||||
|             }; | ||||
|             const multiHopFillData = { | ||||
|                 tokens: [ETH_TOKEN_ADDRESS, WETH_ADDRESS, LON_ADDRESS], | ||||
|                 sellAmount, | ||||
|                 calls: [wrapEthCall, wethLonBatchFillCall], | ||||
|             }; | ||||
|             const tx = await multiplex | ||||
|                 .multiHopFill(multiHopFillData, constants.ZERO_AMOUNT) | ||||
|                 .awaitTransactionSuccessAsync( | ||||
|                     { from: rfqMaker, gasPrice: 0, value: sellAmount }, | ||||
|                     { shouldValidate: false }, | ||||
|                 ); | ||||
|             logUtils.log(`${tx.gasUsed} gas used`); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -3,25 +3,28 @@ import { | ||||
|     constants, | ||||
|     describe, | ||||
|     expect, | ||||
|     getRandomPortion, | ||||
|     randomAddress, | ||||
|     verifyEventsFromLogs, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { | ||||
|     LimitOrder, | ||||
|     LimitOrderFields, | ||||
|     OrderInfo, | ||||
|     OrderStatus, | ||||
|     RevertErrors, | ||||
|     RfqOrder, | ||||
|     RfqOrderFields, | ||||
| } from '@0x/protocol-utils'; | ||||
| import { LimitOrder, LimitOrderFields, OrderStatus, RevertErrors, RfqOrder, RfqOrderFields } from '@0x/protocol-utils'; | ||||
| import { AnyRevertError, BigNumber } from '@0x/utils'; | ||||
| import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; | ||||
|  | ||||
| import { IZeroExContract, IZeroExEvents } from '../../src/wrappers'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { fullMigrateAsync } from '../utils/migration'; | ||||
| import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders'; | ||||
| import { | ||||
|     assertOrderInfoEquals, | ||||
|     computeLimitOrderFilledAmounts, | ||||
|     computeRfqOrderFilledAmounts, | ||||
|     createExpiry, | ||||
|     getActualFillableTakerTokenAmount, | ||||
|     getFillableMakerTokenAmount, | ||||
|     getRandomLimitOrder, | ||||
|     getRandomRfqOrder, | ||||
|     NativeOrdersTestEnvironment, | ||||
| } from '../utils/orders'; | ||||
| import { TestMintableERC20TokenContract, TestRfqOriginRegistrationContract } from '../wrappers'; | ||||
|  | ||||
| blockchainTests.resets('NativeOrdersFeature', env => { | ||||
| @@ -39,6 +42,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|     let takerToken: TestMintableERC20TokenContract; | ||||
|     let wethToken: TestMintableERC20TokenContract; | ||||
|     let testRfqOriginRegistration: TestRfqOriginRegistrationContract; | ||||
|     let testUtils: NativeOrdersTestEnvironment; | ||||
|  | ||||
|     before(async () => { | ||||
|         let owner; | ||||
| @@ -78,6 +82,16 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             env.txDefaults, | ||||
|             artifacts, | ||||
|         ); | ||||
|         testUtils = new NativeOrdersTestEnvironment( | ||||
|             maker, | ||||
|             taker, | ||||
|             makerToken, | ||||
|             takerToken, | ||||
|             zeroEx, | ||||
|             GAS_PRICE, | ||||
|             SINGLE_PROTOCOL_FEE, | ||||
|             env, | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     function getTestLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder { | ||||
| @@ -105,27 +119,6 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     async function prepareBalancesForOrderAsync(order: LimitOrder | RfqOrder, _taker: string = taker): Promise<void> { | ||||
|         await makerToken.mint(maker, order.makerAmount).awaitTransactionSuccessAsync(); | ||||
|         if ('takerTokenFeeAmount' in order) { | ||||
|             await takerToken | ||||
|                 .mint(_taker, order.takerAmount.plus(order.takerTokenFeeAmount)) | ||||
|                 .awaitTransactionSuccessAsync(); | ||||
|         } else { | ||||
|             await takerToken.mint(_taker, order.takerAmount).awaitTransactionSuccessAsync(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function assertOrderInfoEquals(actual: OrderInfo, expected: OrderInfo): void { | ||||
|         expect(actual.status).to.eq(expected.status); | ||||
|         expect(actual.orderHash).to.eq(expected.orderHash); | ||||
|         expect(actual.takerTokenFilledAmount).to.bignumber.eq(expected.takerTokenFilledAmount); | ||||
|     } | ||||
|  | ||||
|     function createExpiry(deltaSeconds: number = 60): BigNumber { | ||||
|         return new BigNumber(Math.floor(Date.now() / 1000) + deltaSeconds); | ||||
|     } | ||||
|  | ||||
|     describe('getProtocolFeeMultiplier()', () => { | ||||
|         it('returns the protocol fee multiplier', async () => { | ||||
|             const r = await zeroEx.getProtocolFeeMultiplier().callAsync(); | ||||
| @@ -149,26 +142,6 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     async function fillLimitOrderAsync( | ||||
|         order: LimitOrder, | ||||
|         opts: Partial<{ | ||||
|             fillAmount: BigNumber | number; | ||||
|             taker: string; | ||||
|             protocolFee?: BigNumber | number; | ||||
|         }> = {}, | ||||
|     ): Promise<TransactionReceiptWithDecodedLogs> { | ||||
|         const { fillAmount, taker: _taker, protocolFee } = { | ||||
|             taker, | ||||
|             fillAmount: order.takerAmount, | ||||
|             ...opts, | ||||
|         }; | ||||
|         await prepareBalancesForOrderAsync(order, _taker); | ||||
|         const _protocolFee = protocolFee === undefined ? SINGLE_PROTOCOL_FEE : protocolFee; | ||||
|         return zeroEx | ||||
|             .fillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount)) | ||||
|             .awaitTransactionSuccessAsync({ from: _taker, value: _protocolFee }); | ||||
|     } | ||||
|  | ||||
|     describe('getLimitOrderInfo()', () => { | ||||
|         it('unfilled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
| @@ -205,7 +178,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const expiry = createExpiry(60); | ||||
|             const order = getTestLimitOrder({ expiry }); | ||||
|             // Fill the order first. | ||||
|             await fillLimitOrderAsync(order); | ||||
|             await testUtils.fillLimitOrderAsync(order); | ||||
|             // Advance time to expire the order. | ||||
|             await env.web3Wrapper.increaseTimeAsync(61); | ||||
|             const info = await zeroEx.getLimitOrderInfo(order).callAsync(); | ||||
| @@ -219,7 +192,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('filled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             // Fill the order first. | ||||
|             await fillLimitOrderAsync(order); | ||||
|             await testUtils.fillLimitOrderAsync(order); | ||||
|             const info = await zeroEx.getLimitOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
|                 status: OrderStatus.Filled, | ||||
| @@ -232,7 +205,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             // Fill the order first. | ||||
|             await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             const info = await zeroEx.getLimitOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
|                 status: OrderStatus.Fillable, | ||||
| @@ -244,7 +217,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('filled then cancelled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             // Fill the order first. | ||||
|             await fillLimitOrderAsync(order); | ||||
|             await testUtils.fillLimitOrderAsync(order); | ||||
|             await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const info = await zeroEx.getLimitOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
| @@ -258,7 +231,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             // Fill the order first. | ||||
|             await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const info = await zeroEx.getLimitOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
| @@ -269,17 +242,6 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     async function fillRfqOrderAsync( | ||||
|         order: RfqOrder, | ||||
|         fillAmount: BigNumber | number = order.takerAmount, | ||||
|         _taker: string = taker, | ||||
|     ): Promise<TransactionReceiptWithDecodedLogs> { | ||||
|         await prepareBalancesForOrderAsync(order, _taker); | ||||
|         return zeroEx | ||||
|             .fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount)) | ||||
|             .awaitTransactionSuccessAsync({ from: _taker }); | ||||
|     } | ||||
|  | ||||
|     describe('getRfqOrderInfo()', () => { | ||||
|         it('unfilled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
| @@ -316,7 +278,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('filled then expired order', async () => { | ||||
|             const expiry = createExpiry(60); | ||||
|             const order = getTestRfqOrder({ expiry }); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const sig = await order.getSignatureWithProviderAsync(env.provider); | ||||
|             // Fill the order first. | ||||
|             await zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker }); | ||||
| @@ -333,7 +295,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('filled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             // Fill the order first. | ||||
|             await fillRfqOrderAsync(order, order.takerAmount, taker); | ||||
|             await testUtils.fillRfqOrderAsync(order, order.takerAmount, taker); | ||||
|             const info = await zeroEx.getRfqOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
|                 status: OrderStatus.Filled, | ||||
| @@ -346,7 +308,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             // Fill the order first. | ||||
|             await fillRfqOrderAsync(order, fillAmount); | ||||
|             await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             const info = await zeroEx.getRfqOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
|                 status: OrderStatus.Fillable, | ||||
| @@ -358,7 +320,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('filled then cancelled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             // Fill the order first. | ||||
|             await fillRfqOrderAsync(order); | ||||
|             await testUtils.fillRfqOrderAsync(order); | ||||
|             await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const info = await zeroEx.getRfqOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
| @@ -372,7 +334,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             // Fill the order first. | ||||
|             await fillRfqOrderAsync(order, fillAmount); | ||||
|             await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const info = await zeroEx.getRfqOrderInfo(order).callAsync(); | ||||
|             assertOrderInfoEquals(info, { | ||||
| @@ -408,7 +370,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('can cancel a fully filled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await fillLimitOrderAsync(order); | ||||
|             await testUtils.fillLimitOrderAsync(order); | ||||
|             const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
| @@ -421,7 +383,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('can cancel a partially filled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await fillLimitOrderAsync(order, { fillAmount: order.takerAmount.minus(1) }); | ||||
|             await testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount.minus(1) }); | ||||
|             const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
| @@ -482,7 +444,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('can cancel a fully filled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await fillRfqOrderAsync(order); | ||||
|             await testUtils.fillRfqOrderAsync(order); | ||||
|             const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
| @@ -495,7 +457,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('can cancel a partially filled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await fillRfqOrderAsync(order, order.takerAmount.minus(1)); | ||||
|             await testUtils.fillRfqOrderAsync(order, order.takerAmount.minus(1)); | ||||
|             const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
| @@ -747,63 +709,6 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     interface LimitOrderFilledAmounts { | ||||
|         makerTokenFilledAmount: BigNumber; | ||||
|         takerTokenFilledAmount: BigNumber; | ||||
|         takerTokenFeeFilledAmount: BigNumber; | ||||
|     } | ||||
|  | ||||
|     function computeLimitOrderFilledAmounts( | ||||
|         order: LimitOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): LimitOrderFilledAmounts { | ||||
|         const fillAmount = BigNumber.min( | ||||
|             order.takerAmount, | ||||
|             takerTokenFillAmount, | ||||
|             order.takerAmount.minus(takerTokenAlreadyFilledAmount), | ||||
|         ); | ||||
|         const makerTokenFilledAmount = fillAmount | ||||
|             .times(order.makerAmount) | ||||
|             .div(order.takerAmount) | ||||
|             .integerValue(BigNumber.ROUND_DOWN); | ||||
|         const takerTokenFeeFilledAmount = fillAmount | ||||
|             .times(order.takerTokenFeeAmount) | ||||
|             .div(order.takerAmount) | ||||
|             .integerValue(BigNumber.ROUND_DOWN); | ||||
|         return { | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFilledAmount: fillAmount, | ||||
|             takerTokenFeeFilledAmount, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     function createLimitOrderFilledEventArgs( | ||||
|         order: LimitOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): object { | ||||
|         const { | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFilledAmount, | ||||
|             takerTokenFeeFilledAmount, | ||||
|         } = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount); | ||||
|         const protocolFee = order.taker !== NULL_ADDRESS ? ZERO_AMOUNT : SINGLE_PROTOCOL_FEE; | ||||
|         return { | ||||
|             taker, | ||||
|             takerTokenFilledAmount, | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFeeFilledAmount, | ||||
|             orderHash: order.getHash(), | ||||
|             maker: order.maker, | ||||
|             feeRecipient: order.feeRecipient, | ||||
|             makerToken: order.makerToken, | ||||
|             takerToken: order.takerToken, | ||||
|             protocolFeePaid: protocolFee, | ||||
|             pool: order.pool, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     async function assertExpectedFinalBalancesFromLimitOrderFillAsync( | ||||
|         order: LimitOrder, | ||||
|         opts: Partial<{ | ||||
| @@ -841,10 +746,10 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|     describe('fillLimitOrder()', () => { | ||||
|         it('can fully fill an order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const receipt = await fillLimitOrderAsync(order); | ||||
|             const receipt = await testUtils.fillLimitOrderAsync(order); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { | ||||
| @@ -858,10 +763,10 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('can partially fill an order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             const receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             const receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { | ||||
| @@ -869,24 +774,26 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|                 status: OrderStatus.Fillable, | ||||
|                 takerTokenFilledAmount: fillAmount, | ||||
|             }); | ||||
|             await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount }); | ||||
|             await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { | ||||
|                 takerTokenFillAmount: fillAmount, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('can fully fill an order in two steps', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             let fillAmount = order.takerAmount.dividedToIntegerBy(2); | ||||
|             let receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             let receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             const alreadyFilledAmount = fillAmount; | ||||
|             fillAmount = order.takerAmount.minus(fillAmount); | ||||
|             receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { | ||||
| @@ -899,10 +806,10 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('clamps fill amount to remaining available', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const fillAmount = order.takerAmount.plus(1); | ||||
|             const receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             const receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { | ||||
| @@ -910,24 +817,26 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|                 status: OrderStatus.Filled, | ||||
|                 takerTokenFilledAmount: order.takerAmount, | ||||
|             }); | ||||
|             await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount }); | ||||
|             await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { | ||||
|                 takerTokenFillAmount: fillAmount, | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('clamps fill amount to remaining available in partial filled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             let fillAmount = order.takerAmount.dividedToIntegerBy(2); | ||||
|             let receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             let receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             const alreadyFilledAmount = fillAmount; | ||||
|             fillAmount = order.takerAmount.minus(fillAmount).plus(1); | ||||
|             receipt = await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { | ||||
| @@ -939,7 +848,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('cannot fill an expired order', async () => { | ||||
|             const order = getTestLimitOrder({ expiry: createExpiry(-60) }); | ||||
|             const tx = fillLimitOrderAsync(order); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired), | ||||
|             ); | ||||
| @@ -948,7 +857,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('cannot fill a cancelled order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const tx = fillLimitOrderAsync(order); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), | ||||
|             ); | ||||
| @@ -959,7 +868,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             await zeroEx | ||||
|                 .cancelPairLimitOrders(makerToken.address, takerToken.address, order.salt.plus(1)) | ||||
|                 .awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const tx = fillLimitOrderAsync(order); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), | ||||
|             ); | ||||
| @@ -967,7 +876,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('non-taker cannot fill order', async () => { | ||||
|             const order = getTestLimitOrder({ taker }); | ||||
|             const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker), | ||||
|             ); | ||||
| @@ -975,7 +884,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('non-sender cannot fill order', async () => { | ||||
|             const order = getTestLimitOrder({ sender: taker }); | ||||
|             const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableBySenderError(order.getHash(), notTaker, order.sender), | ||||
|             ); | ||||
| @@ -985,7 +894,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             // Overwrite chainId to result in a different hash and therefore different | ||||
|             // signature. | ||||
|             const tx = fillLimitOrderAsync(order.clone({ chainId: 1234 })); | ||||
|             const tx = testUtils.fillLimitOrderAsync(order.clone({ chainId: 1234 })); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker), | ||||
|             ); | ||||
| @@ -993,7 +902,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('fails if no protocol fee attached', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const tx = zeroEx | ||||
|                 .fillLimitOrder( | ||||
|                     order, | ||||
| @@ -1008,62 +917,24 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('refunds excess protocol fee', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             const receipt = await fillLimitOrderAsync(order, { protocolFee: SINGLE_PROTOCOL_FEE.plus(1) }); | ||||
|             const receipt = await testUtils.fillLimitOrderAsync(order, { protocolFee: SINGLE_PROTOCOL_FEE.plus(1) }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|             await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { receipt }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     interface RfqOrderFilledAmounts { | ||||
|         makerTokenFilledAmount: BigNumber; | ||||
|         takerTokenFilledAmount: BigNumber; | ||||
|     } | ||||
|  | ||||
|     function computeRfqOrderFilledAmounts( | ||||
|         order: RfqOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): RfqOrderFilledAmounts { | ||||
|         const fillAmount = BigNumber.min( | ||||
|             order.takerAmount, | ||||
|             takerTokenFillAmount, | ||||
|             order.takerAmount.minus(takerTokenAlreadyFilledAmount), | ||||
|         ); | ||||
|         const makerTokenFilledAmount = fillAmount | ||||
|             .times(order.makerAmount) | ||||
|             .div(order.takerAmount) | ||||
|             .integerValue(BigNumber.ROUND_DOWN); | ||||
|         return { | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFilledAmount: fillAmount, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     function createRfqOrderFilledEventArgs( | ||||
|         order: RfqOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): object { | ||||
|         const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts( | ||||
|             order, | ||||
|             takerTokenFillAmount, | ||||
|             takerTokenAlreadyFilledAmount, | ||||
|         ); | ||||
|         return { | ||||
|             taker, | ||||
|             takerTokenFilledAmount, | ||||
|             makerTokenFilledAmount, | ||||
|             orderHash: order.getHash(), | ||||
|             maker: order.maker, | ||||
|             makerToken: order.makerToken, | ||||
|             takerToken: order.takerToken, | ||||
|             pool: order.pool, | ||||
|         }; | ||||
|     } | ||||
|     describe('registerAllowedRfqOrigins()', () => { | ||||
|         it('cannot register through a contract', async () => { | ||||
|             const tx = testRfqOriginRegistration | ||||
|                 .registerAllowedRfqOrigins(zeroEx.address, [], true) | ||||
|                 .awaitTransactionSuccessAsync(); | ||||
|             expect(tx).to.revertWith('NativeOrdersFeature/NO_CONTRACT_ORIGINS'); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     async function assertExpectedFinalBalancesFromRfqOrderFillAsync( | ||||
|         order: RfqOrder, | ||||
| @@ -1081,20 +952,15 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount); | ||||
|     } | ||||
|  | ||||
|     describe('registerAllowedRfqOrigins()', () => { | ||||
|         it('cannot register through a contract', async () => { | ||||
|             const tx = testRfqOriginRegistration | ||||
|                 .registerAllowedRfqOrigins(zeroEx.address, [], true) | ||||
|                 .awaitTransactionSuccessAsync(); | ||||
|             expect(tx).to.revertWith('NativeOrdersFeature/NO_CONTRACT_ORIGINS'); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('fillRfqOrder()', () => { | ||||
|         it('can fully fill an order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const receipt = await fillRfqOrderAsync(order); | ||||
|             verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled); | ||||
|             const receipt = await testUtils.fillRfqOrderAsync(order); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { | ||||
|                 orderHash: order.getHash(), | ||||
|                 status: OrderStatus.Filled, | ||||
| @@ -1106,10 +972,10 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('can partially fill an order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const fillAmount = order.takerAmount.minus(1); | ||||
|             const receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             const receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { | ||||
| @@ -1123,18 +989,18 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('can fully fill an order in two steps', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             let fillAmount = order.takerAmount.dividedToIntegerBy(2); | ||||
|             let receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             let receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             const alreadyFilledAmount = fillAmount; | ||||
|             fillAmount = order.takerAmount.minus(fillAmount); | ||||
|             receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { | ||||
| @@ -1147,10 +1013,10 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('clamps fill amount to remaining available', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const fillAmount = order.takerAmount.plus(1); | ||||
|             const receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             const receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { | ||||
| @@ -1164,18 +1030,18 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('clamps fill amount to remaining available in partial filled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             let fillAmount = order.takerAmount.dividedToIntegerBy(2); | ||||
|             let receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             let receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             const alreadyFilledAmount = fillAmount; | ||||
|             fillAmount = order.takerAmount.minus(fillAmount).plus(1); | ||||
|             receipt = await fillRfqOrderAsync(order, fillAmount); | ||||
|             receipt = await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|             assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { | ||||
| @@ -1187,7 +1053,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('cannot fill an order with wrong tx.origin', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker), | ||||
|             ); | ||||
| @@ -1210,7 +1076,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|                 ], | ||||
|                 IZeroExEvents.RfqOrderOriginsAllowed, | ||||
|             ); | ||||
|             return fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             return testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|         }); | ||||
|  | ||||
|         it('cannot fill an order with registered then unregistered tx.origin', async () => { | ||||
| @@ -1232,7 +1098,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|                 IZeroExEvents.RfqOrderOriginsAllowed, | ||||
|             ); | ||||
|  | ||||
|             const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker), | ||||
|             ); | ||||
| @@ -1240,7 +1106,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('cannot fill an order with a zero tx.origin', async () => { | ||||
|             const order = getTestRfqOrder({ txOrigin: NULL_ADDRESS }); | ||||
|             const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Invalid), | ||||
|             ); | ||||
| @@ -1248,7 +1114,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('non-taker cannot fill order', async () => { | ||||
|             const order = getTestRfqOrder({ taker, txOrigin: notTaker }); | ||||
|             const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker), | ||||
|             ); | ||||
| @@ -1256,7 +1122,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('cannot fill an expired order', async () => { | ||||
|             const order = getTestRfqOrder({ expiry: createExpiry(-60) }); | ||||
|             const tx = fillRfqOrderAsync(order); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired), | ||||
|             ); | ||||
| @@ -1265,7 +1131,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         it('cannot fill a cancelled order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const tx = fillRfqOrderAsync(order); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), | ||||
|             ); | ||||
| @@ -1276,7 +1142,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             await zeroEx | ||||
|                 .cancelPairRfqOrders(makerToken.address, takerToken.address, order.salt.plus(1)) | ||||
|                 .awaitTransactionSuccessAsync({ from: maker }); | ||||
|             const tx = fillRfqOrderAsync(order); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), | ||||
|             ); | ||||
| @@ -1286,7 +1152,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             // Overwrite chainId to result in a different hash and therefore different | ||||
|             // signature. | ||||
|             const tx = fillRfqOrderAsync(order.clone({ chainId: 1234 })); | ||||
|             const tx = testUtils.fillRfqOrderAsync(order.clone({ chainId: 1234 })); | ||||
|             return expect(tx).to.revertWith( | ||||
|                 new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker), | ||||
|             ); | ||||
| @@ -1294,7 +1160,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('fails if ETH is attached', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await prepareBalancesForOrderAsync(order, taker); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order], taker); | ||||
|             const tx = zeroEx | ||||
|                 .fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value: 1 }); | ||||
| @@ -1306,20 +1172,20 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|     describe('fillOrKillLimitOrder()', () => { | ||||
|         it('can fully fill an order', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const receipt = await zeroEx | ||||
|                 .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE }); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [createLimitOrderFilledEventArgs(order)], | ||||
|                 [testUtils.createLimitOrderFilledEventArgs(order)], | ||||
|                 IZeroExEvents.LimitOrderFilled, | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('reverts if cannot fill the exact amount', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const fillAmount = order.takerAmount.plus(1); | ||||
|             const tx = zeroEx | ||||
|                 .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount) | ||||
| @@ -1331,7 +1197,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('refunds excess protocol fee', async () => { | ||||
|             const order = getTestLimitOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const takerBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker); | ||||
|             const receipt = await zeroEx | ||||
|                 .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) | ||||
| @@ -1345,16 +1211,20 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|     describe('fillOrKillRfqOrder()', () => { | ||||
|         it('can fully fill an order', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const receipt = await zeroEx | ||||
|                 .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker }); | ||||
|             verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled); | ||||
|             verifyEventsFromLogs( | ||||
|                 receipt.logs, | ||||
|                 [testUtils.createRfqOrderFilledEventArgs(order)], | ||||
|                 IZeroExEvents.RfqOrderFilled, | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it('reverts if cannot fill the exact amount', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const fillAmount = order.takerAmount.plus(1); | ||||
|             const tx = zeroEx | ||||
|                 .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount) | ||||
| @@ -1366,7 +1236,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|  | ||||
|         it('fails if ETH is attached', async () => { | ||||
|             const order = getTestRfqOrder(); | ||||
|             await prepareBalancesForOrderAsync(order); | ||||
|             await testUtils.prepareBalancesForOrdersAsync([order]); | ||||
|             const tx = zeroEx | ||||
|                 .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) | ||||
|                 .awaitTransactionSuccessAsync({ from: taker, value: 1 }); | ||||
| @@ -1385,34 +1255,6 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|         await makerToken.approve(zeroEx.address, allowance).awaitTransactionSuccessAsync({ from: maker }); | ||||
|     } | ||||
|  | ||||
|     function getFillableMakerTokenAmount( | ||||
|         order: LimitOrder | RfqOrder, | ||||
|         takerTokenFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): BigNumber { | ||||
|         return order.takerAmount | ||||
|             .minus(takerTokenFilledAmount) | ||||
|             .times(order.makerAmount) | ||||
|             .div(order.takerAmount) | ||||
|             .integerValue(BigNumber.ROUND_DOWN); | ||||
|     } | ||||
|  | ||||
|     function getActualFillableTakerTokenAmount( | ||||
|         order: LimitOrder | RfqOrder, | ||||
|         makerBalance: BigNumber = order.makerAmount, | ||||
|         makerAllowance: BigNumber = order.makerAmount, | ||||
|         takerTokenFilledAmount: BigNumber = ZERO_AMOUNT, | ||||
|     ): BigNumber { | ||||
|         const fillableMakerTokenAmount = getFillableMakerTokenAmount(order, takerTokenFilledAmount); | ||||
|         return BigNumber.min(fillableMakerTokenAmount, makerBalance, makerAllowance) | ||||
|             .times(order.takerAmount) | ||||
|             .div(order.makerAmount) | ||||
|             .integerValue(BigNumber.ROUND_UP); | ||||
|     } | ||||
|  | ||||
|     function getRandomFraction(precision: number = 2): string { | ||||
|         return Math.random().toPrecision(precision); | ||||
|     } | ||||
|  | ||||
|     describe('getLimitOrderRelevantState()', () => { | ||||
|         it('works with an empty order', async () => { | ||||
|             const order = getTestLimitOrder({ | ||||
| @@ -1487,7 +1329,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             await takerToken | ||||
|                 .mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount)) | ||||
|                 .awaitTransactionSuccessAsync(); | ||||
|             await fillLimitOrderAsync(order); | ||||
|             await testUtils.fillLimitOrderAsync(order); | ||||
|             // Partially fill the order. | ||||
|             const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx | ||||
|                 .getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) | ||||
| @@ -1509,12 +1351,12 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|                 .mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount)) | ||||
|                 .awaitTransactionSuccessAsync(); | ||||
|             // Partially fill the order. | ||||
|             const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue(); | ||||
|             await fillLimitOrderAsync(order, { fillAmount }); | ||||
|             const fillAmount = getRandomPortion(order.takerAmount); | ||||
|             await testUtils.fillLimitOrderAsync(order, { fillAmount }); | ||||
|             // Reduce maker funds to be < remaining. | ||||
|             const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount); | ||||
|             const balance = remainingMakerAmount.times(getRandomFraction()).integerValue(); | ||||
|             const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue(); | ||||
|             const balance = getRandomPortion(remainingMakerAmount); | ||||
|             const allowance = getRandomPortion(remainingMakerAmount); | ||||
|             await fundOrderMakerAsync(order, balance, allowance); | ||||
|             // Get order state. | ||||
|             const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx | ||||
| @@ -1604,7 +1446,7 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             // Fully Fund maker and taker. | ||||
|             await fundOrderMakerAsync(order); | ||||
|             await takerToken.mint(taker, order.takerAmount); | ||||
|             await fillRfqOrderAsync(order); | ||||
|             await testUtils.fillRfqOrderAsync(order); | ||||
|             // Partially fill the order. | ||||
|             const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx | ||||
|                 .getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider)) | ||||
| @@ -1624,12 +1466,12 @@ blockchainTests.resets('NativeOrdersFeature', env => { | ||||
|             await fundOrderMakerAsync(order); | ||||
|             await takerToken.mint(taker, order.takerAmount).awaitTransactionSuccessAsync(); | ||||
|             // Partially fill the order. | ||||
|             const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue(); | ||||
|             await fillRfqOrderAsync(order, fillAmount); | ||||
|             const fillAmount = getRandomPortion(order.takerAmount); | ||||
|             await testUtils.fillRfqOrderAsync(order, fillAmount); | ||||
|             // Reduce maker funds to be < remaining. | ||||
|             const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount); | ||||
|             const balance = remainingMakerAmount.times(getRandomFraction()).integerValue(); | ||||
|             const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue(); | ||||
|             const balance = getRandomPortion(remainingMakerAmount); | ||||
|             const allowance = getRandomPortion(remainingMakerAmount); | ||||
|             await fundOrderMakerAsync(order, balance, allowance); | ||||
|             // Get order state. | ||||
|             const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx | ||||
|   | ||||
| @@ -1,6 +1,181 @@ | ||||
| import { getRandomInteger, randomAddress } from '@0x/contracts-test-utils'; | ||||
| import { LimitOrder, LimitOrderFields, RfqOrder, RfqOrderFields } from '@0x/protocol-utils'; | ||||
| import { | ||||
|     BlockchainTestsEnvironment, | ||||
|     constants, | ||||
|     expect, | ||||
|     getRandomInteger, | ||||
|     randomAddress, | ||||
| } from '@0x/contracts-test-utils'; | ||||
| import { LimitOrder, LimitOrderFields, OrderBase, OrderInfo, RfqOrder, RfqOrderFields } from '@0x/protocol-utils'; | ||||
| import { BigNumber, hexUtils } from '@0x/utils'; | ||||
| import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; | ||||
|  | ||||
| import { IZeroExContract, IZeroExLimitOrderFilledEventArgs, IZeroExRfqOrderFilledEventArgs } from '../../src/wrappers'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { fullMigrateAsync } from '../utils/migration'; | ||||
| import { TestMintableERC20TokenContract } from '../wrappers'; | ||||
|  | ||||
| const { ZERO_AMOUNT: ZERO, NULL_ADDRESS } = constants; | ||||
|  | ||||
| interface RfqOrderFilledAmounts { | ||||
|     makerTokenFilledAmount: BigNumber; | ||||
|     takerTokenFilledAmount: BigNumber; | ||||
| } | ||||
|  | ||||
| interface LimitOrderFilledAmounts { | ||||
|     makerTokenFilledAmount: BigNumber; | ||||
|     takerTokenFilledAmount: BigNumber; | ||||
|     takerTokenFeeFilledAmount: BigNumber; | ||||
| } | ||||
|  | ||||
| export class NativeOrdersTestEnvironment { | ||||
|     public static async createAsync( | ||||
|         env: BlockchainTestsEnvironment, | ||||
|         gasPrice: BigNumber = new BigNumber('123e9'), | ||||
|         protocolFeeMultiplier: number = 70e3, | ||||
|     ): Promise<NativeOrdersTestEnvironment> { | ||||
|         const [owner, maker, taker] = await env.getAccountAddressesAsync(); | ||||
|         const [makerToken, takerToken] = await Promise.all( | ||||
|             [...new Array(2)].map(async () => | ||||
|                 TestMintableERC20TokenContract.deployFrom0xArtifactAsync( | ||||
|                     artifacts.TestMintableERC20Token, | ||||
|                     env.provider, | ||||
|                     { ...env.txDefaults, gasPrice }, | ||||
|                     artifacts, | ||||
|                 ), | ||||
|             ), | ||||
|         ); | ||||
|         const zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {}, { protocolFeeMultiplier }); | ||||
|         await makerToken.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: maker }); | ||||
|         await takerToken.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }); | ||||
|         return new NativeOrdersTestEnvironment( | ||||
|             maker, | ||||
|             taker, | ||||
|             makerToken, | ||||
|             takerToken, | ||||
|             zeroEx, | ||||
|             gasPrice, | ||||
|             gasPrice.times(protocolFeeMultiplier), | ||||
|             env, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     constructor( | ||||
|         public readonly maker: string, | ||||
|         public readonly taker: string, | ||||
|         public readonly makerToken: TestMintableERC20TokenContract, | ||||
|         public readonly takerToken: TestMintableERC20TokenContract, | ||||
|         public readonly zeroEx: IZeroExContract, | ||||
|         public readonly gasPrice: BigNumber, | ||||
|         public readonly protocolFee: BigNumber, | ||||
|         private readonly _env: BlockchainTestsEnvironment, | ||||
|     ) {} | ||||
|  | ||||
|     public async prepareBalancesForOrdersAsync( | ||||
|         orders: LimitOrder[] | RfqOrder[], | ||||
|         taker: string = this.taker, | ||||
|     ): Promise<void> { | ||||
|         await this.makerToken | ||||
|             .mint(this.maker, BigNumber.sum(...(orders as OrderBase[]).map(order => order.makerAmount))) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|         await this.takerToken | ||||
|             .mint( | ||||
|                 taker, | ||||
|                 BigNumber.sum( | ||||
|                     ...(orders as OrderBase[]).map(order => | ||||
|                         order.takerAmount.plus(order instanceof LimitOrder ? order.takerTokenFeeAmount : 0), | ||||
|                     ), | ||||
|                 ), | ||||
|             ) | ||||
|             .awaitTransactionSuccessAsync(); | ||||
|     } | ||||
|  | ||||
|     public async fillLimitOrderAsync( | ||||
|         order: LimitOrder, | ||||
|         opts: Partial<{ | ||||
|             fillAmount: BigNumber | number; | ||||
|             taker: string; | ||||
|             protocolFee: BigNumber | number; | ||||
|         }> = {}, | ||||
|     ): Promise<TransactionReceiptWithDecodedLogs> { | ||||
|         const { fillAmount, taker, protocolFee } = { | ||||
|             taker: this.taker, | ||||
|             fillAmount: order.takerAmount, | ||||
|             ...opts, | ||||
|         }; | ||||
|         await this.prepareBalancesForOrdersAsync([order], taker); | ||||
|         const value = protocolFee === undefined ? this.protocolFee : protocolFee; | ||||
|         return this.zeroEx | ||||
|             .fillLimitOrder( | ||||
|                 order, | ||||
|                 await order.getSignatureWithProviderAsync(this._env.provider), | ||||
|                 new BigNumber(fillAmount), | ||||
|             ) | ||||
|             .awaitTransactionSuccessAsync({ from: taker, value }); | ||||
|     } | ||||
|  | ||||
|     public async fillRfqOrderAsync( | ||||
|         order: RfqOrder, | ||||
|         fillAmount: BigNumber | number = order.takerAmount, | ||||
|         taker: string = this.taker, | ||||
|     ): Promise<TransactionReceiptWithDecodedLogs> { | ||||
|         await this.prepareBalancesForOrdersAsync([order], taker); | ||||
|         return this.zeroEx | ||||
|             .fillRfqOrder( | ||||
|                 order, | ||||
|                 await order.getSignatureWithProviderAsync(this._env.provider), | ||||
|                 new BigNumber(fillAmount), | ||||
|             ) | ||||
|             .awaitTransactionSuccessAsync({ from: taker }); | ||||
|     } | ||||
|  | ||||
|     public createLimitOrderFilledEventArgs( | ||||
|         order: LimitOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO, | ||||
|     ): IZeroExLimitOrderFilledEventArgs { | ||||
|         const { | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFilledAmount, | ||||
|             takerTokenFeeFilledAmount, | ||||
|         } = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount); | ||||
|         const protocolFee = order.taker !== NULL_ADDRESS ? ZERO : this.protocolFee; | ||||
|         return { | ||||
|             takerTokenFilledAmount, | ||||
|             makerTokenFilledAmount, | ||||
|             takerTokenFeeFilledAmount, | ||||
|             orderHash: order.getHash(), | ||||
|             maker: order.maker, | ||||
|             taker: this.taker, | ||||
|             feeRecipient: order.feeRecipient, | ||||
|             makerToken: order.makerToken, | ||||
|             takerToken: order.takerToken, | ||||
|             protocolFeePaid: protocolFee, | ||||
|             pool: order.pool, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public createRfqOrderFilledEventArgs( | ||||
|         order: RfqOrder, | ||||
|         takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|         takerTokenAlreadyFilledAmount: BigNumber = ZERO, | ||||
|     ): IZeroExRfqOrderFilledEventArgs { | ||||
|         const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts( | ||||
|             order, | ||||
|             takerTokenFillAmount, | ||||
|             takerTokenAlreadyFilledAmount, | ||||
|         ); | ||||
|         return { | ||||
|             takerTokenFilledAmount, | ||||
|             makerTokenFilledAmount, | ||||
|             orderHash: order.getHash(), | ||||
|             maker: order.maker, | ||||
|             taker: this.taker, | ||||
|             makerToken: order.makerToken, | ||||
|             takerToken: order.takerToken, | ||||
|             pool: order.pool, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Generate a random limit order. | ||||
| @@ -40,3 +215,105 @@ export function getRandomRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrde | ||||
|         ...fields, | ||||
|     }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Asserts the fields of an OrderInfo object. | ||||
|  */ | ||||
| export function assertOrderInfoEquals(actual: OrderInfo, expected: OrderInfo): void { | ||||
|     expect(actual.status, 'Order status').to.eq(expected.status); | ||||
|     expect(actual.orderHash, 'Order hash').to.eq(expected.orderHash); | ||||
|     expect(actual.takerTokenFilledAmount, 'Order takerTokenFilledAmount').to.bignumber.eq( | ||||
|         expected.takerTokenFilledAmount, | ||||
|     ); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates an order expiry field. | ||||
|  */ | ||||
| export function createExpiry(deltaSeconds: number = 60): BigNumber { | ||||
|     return new BigNumber(Math.floor(Date.now() / 1000) + deltaSeconds); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Computes the maker, taker, and taker token fee amounts filled for | ||||
|  * the given limit order. | ||||
|  */ | ||||
| export function computeLimitOrderFilledAmounts( | ||||
|     order: LimitOrder, | ||||
|     takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|     takerTokenAlreadyFilledAmount: BigNumber = ZERO, | ||||
| ): LimitOrderFilledAmounts { | ||||
|     const fillAmount = BigNumber.min( | ||||
|         order.takerAmount, | ||||
|         takerTokenFillAmount, | ||||
|         order.takerAmount.minus(takerTokenAlreadyFilledAmount), | ||||
|     ); | ||||
|     const makerTokenFilledAmount = fillAmount | ||||
|         .times(order.makerAmount) | ||||
|         .div(order.takerAmount) | ||||
|         .integerValue(BigNumber.ROUND_DOWN); | ||||
|     const takerTokenFeeFilledAmount = fillAmount | ||||
|         .times(order.takerTokenFeeAmount) | ||||
|         .div(order.takerAmount) | ||||
|         .integerValue(BigNumber.ROUND_DOWN); | ||||
|     return { | ||||
|         makerTokenFilledAmount, | ||||
|         takerTokenFilledAmount: fillAmount, | ||||
|         takerTokenFeeFilledAmount, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Computes the maker and taker amounts filled for the given RFQ order. | ||||
|  */ | ||||
| export function computeRfqOrderFilledAmounts( | ||||
|     order: RfqOrder, | ||||
|     takerTokenFillAmount: BigNumber = order.takerAmount, | ||||
|     takerTokenAlreadyFilledAmount: BigNumber = ZERO, | ||||
| ): RfqOrderFilledAmounts { | ||||
|     const fillAmount = BigNumber.min( | ||||
|         order.takerAmount, | ||||
|         takerTokenFillAmount, | ||||
|         order.takerAmount.minus(takerTokenAlreadyFilledAmount), | ||||
|     ); | ||||
|     const makerTokenFilledAmount = fillAmount | ||||
|         .times(order.makerAmount) | ||||
|         .div(order.takerAmount) | ||||
|         .integerValue(BigNumber.ROUND_DOWN); | ||||
|     return { | ||||
|         makerTokenFilledAmount, | ||||
|         takerTokenFilledAmount: fillAmount, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Computes the remaining fillable amount in maker token for | ||||
|  * the given order. | ||||
|  */ | ||||
| export function getFillableMakerTokenAmount( | ||||
|     order: LimitOrder | RfqOrder, | ||||
|     takerTokenFilledAmount: BigNumber = ZERO, | ||||
| ): BigNumber { | ||||
|     return order.takerAmount | ||||
|         .minus(takerTokenFilledAmount) | ||||
|         .times(order.makerAmount) | ||||
|         .div(order.takerAmount) | ||||
|         .integerValue(BigNumber.ROUND_DOWN); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Computes the remaining fillable amnount in taker token, based on | ||||
|  * the amount already filled and the maker's balance/allowance. | ||||
|  */ | ||||
| export function getActualFillableTakerTokenAmount( | ||||
|     order: LimitOrder | RfqOrder, | ||||
|     makerBalance: BigNumber = order.makerAmount, | ||||
|     makerAllowance: BigNumber = order.makerAmount, | ||||
|     takerTokenFilledAmount: BigNumber = ZERO, | ||||
| ): BigNumber { | ||||
|     const fillableMakerTokenAmount = getFillableMakerTokenAmount(order, takerTokenFilledAmount); | ||||
|     return BigNumber.min(fillableMakerTokenAmount, makerBalance, makerAllowance) | ||||
|         .times(order.takerAmount) | ||||
|         .div(order.makerAmount) | ||||
|         .integerValue(BigNumber.ROUND_UP); | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/affiliate_fee_transformer'; | ||||
| export * from '../test/generated-wrappers/allowance_target'; | ||||
| export * from '../test/generated-wrappers/batch_fill_native_orders_feature'; | ||||
| export * from '../test/generated-wrappers/bootstrap_feature'; | ||||
| export * from '../test/generated-wrappers/bridge_adapter'; | ||||
| export * from '../test/generated-wrappers/bridge_source'; | ||||
| @@ -20,6 +21,7 @@ export * from '../test/generated-wrappers/fixin_token_spender'; | ||||
| 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_batch_fill_native_orders_feature'; | ||||
| export * from '../test/generated-wrappers/i_bootstrap_feature'; | ||||
| export * from '../test/generated-wrappers/i_bridge_adapter'; | ||||
| export * from '../test/generated-wrappers/i_erc20_bridge'; | ||||
| @@ -31,6 +33,8 @@ export * from '../test/generated-wrappers/i_liquidity_provider_feature'; | ||||
| export * from '../test/generated-wrappers/i_liquidity_provider_sandbox'; | ||||
| export * from '../test/generated-wrappers/i_meta_transactions_feature'; | ||||
| export * from '../test/generated-wrappers/i_mooniswap_pool'; | ||||
| export * from '../test/generated-wrappers/i_multiplex_feature'; | ||||
| export * from '../test/generated-wrappers/i_native_orders_events'; | ||||
| export * from '../test/generated-wrappers/i_native_orders_feature'; | ||||
| export * from '../test/generated-wrappers/i_ownable_feature'; | ||||
| export * from '../test/generated-wrappers/i_simple_function_registry_feature'; | ||||
| @@ -39,6 +43,7 @@ export * from '../test/generated-wrappers/i_test_simple_function_registry_featur | ||||
| export * from '../test/generated-wrappers/i_token_spender_feature'; | ||||
| export * from '../test/generated-wrappers/i_transform_erc20_feature'; | ||||
| export * from '../test/generated-wrappers/i_uniswap_feature'; | ||||
| export * from '../test/generated-wrappers/i_uniswap_v2_pair'; | ||||
| export * from '../test/generated-wrappers/i_zero_ex'; | ||||
| export * from '../test/generated-wrappers/initial_migration'; | ||||
| export * from '../test/generated-wrappers/lib_bootstrap'; | ||||
| @@ -88,7 +93,12 @@ export * from '../test/generated-wrappers/mixin_uniswap'; | ||||
| export * from '../test/generated-wrappers/mixin_uniswap_v2'; | ||||
| export * from '../test/generated-wrappers/mixin_zero_ex_bridge'; | ||||
| export * from '../test/generated-wrappers/mooniswap_liquidity_provider'; | ||||
| export * from '../test/generated-wrappers/multiplex_feature'; | ||||
| export * from '../test/generated-wrappers/native_orders_cancellation'; | ||||
| export * from '../test/generated-wrappers/native_orders_feature'; | ||||
| export * from '../test/generated-wrappers/native_orders_info'; | ||||
| export * from '../test/generated-wrappers/native_orders_protocol_fees'; | ||||
| export * from '../test/generated-wrappers/native_orders_settlement'; | ||||
| export * from '../test/generated-wrappers/ownable_feature'; | ||||
| export * from '../test/generated-wrappers/pay_taker_transformer'; | ||||
| export * from '../test/generated-wrappers/permissionless_transformer_deployer'; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*", "./scripts/**/*"], | ||||
|     "files": [ | ||||
|         "generated-artifacts/AffiliateFeeTransformer.json", | ||||
|         "generated-artifacts/BatchFillNativeOrdersFeature.json", | ||||
|         "generated-artifacts/BridgeAdapter.json", | ||||
|         "generated-artifacts/CurveLiquidityProvider.json", | ||||
|         "generated-artifacts/FeeCollector.json", | ||||
| @@ -11,9 +12,11 @@ | ||||
|         "generated-artifacts/FillQuoteTransformer.json", | ||||
|         "generated-artifacts/FullMigration.json", | ||||
|         "generated-artifacts/IAllowanceTarget.json", | ||||
|         "generated-artifacts/IBatchFillNativeOrdersFeature.json", | ||||
|         "generated-artifacts/IERC20Transformer.json", | ||||
|         "generated-artifacts/IFlashWallet.json", | ||||
|         "generated-artifacts/ILiquidityProviderFeature.json", | ||||
|         "generated-artifacts/IMultiplexFeature.json", | ||||
|         "generated-artifacts/INativeOrdersFeature.json", | ||||
|         "generated-artifacts/IOwnableFeature.json", | ||||
|         "generated-artifacts/ISimpleFunctionRegistryFeature.json", | ||||
| @@ -24,6 +27,7 @@ | ||||
|         "generated-artifacts/LiquidityProviderFeature.json", | ||||
|         "generated-artifacts/LogMetadataTransformer.json", | ||||
|         "generated-artifacts/MetaTransactionsFeature.json", | ||||
|         "generated-artifacts/MultiplexFeature.json", | ||||
|         "generated-artifacts/NativeOrdersFeature.json", | ||||
|         "generated-artifacts/OwnableFeature.json", | ||||
|         "generated-artifacts/PayTakerTransformer.json", | ||||
| @@ -35,6 +39,7 @@ | ||||
|         "generated-artifacts/ZeroEx.json", | ||||
|         "test/generated-artifacts/AffiliateFeeTransformer.json", | ||||
|         "test/generated-artifacts/AllowanceTarget.json", | ||||
|         "test/generated-artifacts/BatchFillNativeOrdersFeature.json", | ||||
|         "test/generated-artifacts/BootstrapFeature.json", | ||||
|         "test/generated-artifacts/BridgeAdapter.json", | ||||
|         "test/generated-artifacts/BridgeSource.json", | ||||
| @@ -50,6 +55,7 @@ | ||||
|         "test/generated-artifacts/FlashWallet.json", | ||||
|         "test/generated-artifacts/FullMigration.json", | ||||
|         "test/generated-artifacts/IAllowanceTarget.json", | ||||
|         "test/generated-artifacts/IBatchFillNativeOrdersFeature.json", | ||||
|         "test/generated-artifacts/IBootstrapFeature.json", | ||||
|         "test/generated-artifacts/IBridgeAdapter.json", | ||||
|         "test/generated-artifacts/IERC20Bridge.json", | ||||
| @@ -61,6 +67,8 @@ | ||||
|         "test/generated-artifacts/ILiquidityProviderSandbox.json", | ||||
|         "test/generated-artifacts/IMetaTransactionsFeature.json", | ||||
|         "test/generated-artifacts/IMooniswapPool.json", | ||||
|         "test/generated-artifacts/IMultiplexFeature.json", | ||||
|         "test/generated-artifacts/INativeOrdersEvents.json", | ||||
|         "test/generated-artifacts/INativeOrdersFeature.json", | ||||
|         "test/generated-artifacts/IOwnableFeature.json", | ||||
|         "test/generated-artifacts/ISimpleFunctionRegistryFeature.json", | ||||
| @@ -69,6 +77,7 @@ | ||||
|         "test/generated-artifacts/ITokenSpenderFeature.json", | ||||
|         "test/generated-artifacts/ITransformERC20Feature.json", | ||||
|         "test/generated-artifacts/IUniswapFeature.json", | ||||
|         "test/generated-artifacts/IUniswapV2Pair.json", | ||||
|         "test/generated-artifacts/IZeroEx.json", | ||||
|         "test/generated-artifacts/InitialMigration.json", | ||||
|         "test/generated-artifacts/LibBootstrap.json", | ||||
| @@ -118,7 +127,12 @@ | ||||
|         "test/generated-artifacts/MixinUniswapV2.json", | ||||
|         "test/generated-artifacts/MixinZeroExBridge.json", | ||||
|         "test/generated-artifacts/MooniswapLiquidityProvider.json", | ||||
|         "test/generated-artifacts/MultiplexFeature.json", | ||||
|         "test/generated-artifacts/NativeOrdersCancellation.json", | ||||
|         "test/generated-artifacts/NativeOrdersFeature.json", | ||||
|         "test/generated-artifacts/NativeOrdersInfo.json", | ||||
|         "test/generated-artifacts/NativeOrdersProtocolFees.json", | ||||
|         "test/generated-artifacts/NativeOrdersSettlement.json", | ||||
|         "test/generated-artifacts/OwnableFeature.json", | ||||
|         "test/generated-artifacts/PayTakerTransformer.json", | ||||
|         "test/generated-artifacts/PermissionlessTransformerDeployer.json", | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "3.13.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Update IZeroEx artifact", | ||||
|                 "pr": 140 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "3.12.0", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -3,6 +3,16 @@ | ||||
|     "contractName": "IZeroEx", | ||||
|     "compilerOutput": { | ||||
|         "abi": [ | ||||
|             { | ||||
|                 "anonymous": false, | ||||
|                 "inputs": [ | ||||
|                     { "indexed": false, "internalType": "bytes32", "name": "orderHash", "type": "bytes32" }, | ||||
|                     { "indexed": false, "internalType": "address", "name": "maker", "type": "address" }, | ||||
|                     { "indexed": false, "internalType": "uint64", "name": "expiry", "type": "uint64" } | ||||
|                 ], | ||||
|                 "name": "ExpiredRfqOrder", | ||||
|                 "type": "event" | ||||
|             }, | ||||
|             { | ||||
|                 "anonymous": false, | ||||
|                 "inputs": [ | ||||
| @@ -39,26 +49,11 @@ | ||||
|             { | ||||
|                 "anonymous": false, | ||||
|                 "inputs": [ | ||||
|                     { | ||||
|                         "indexed": false, | ||||
|                         "internalType": "contract IERC20TokenV06", | ||||
|                         "name": "inputToken", | ||||
|                         "type": "address" | ||||
|                     }, | ||||
|                     { | ||||
|                         "indexed": false, | ||||
|                         "internalType": "contract IERC20TokenV06", | ||||
|                         "name": "outputToken", | ||||
|                         "type": "address" | ||||
|                     }, | ||||
|                     { "indexed": false, "internalType": "address", "name": "inputToken", "type": "address" }, | ||||
|                     { "indexed": false, "internalType": "address", "name": "outputToken", "type": "address" }, | ||||
|                     { "indexed": false, "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" }, | ||||
|                     { "indexed": false, "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }, | ||||
|                     { | ||||
|                         "indexed": false, | ||||
|                         "internalType": "contract ILiquidityProvider", | ||||
|                         "name": "provider", | ||||
|                         "type": "address" | ||||
|                     }, | ||||
|                     { "indexed": false, "internalType": "address", "name": "provider", "type": "address" }, | ||||
|                     { "indexed": false, "internalType": "address", "name": "recipient", "type": "address" } | ||||
|                 ], | ||||
|                 "name": "LiquidityProviderSwap", | ||||
| @@ -444,6 +439,127 @@ | ||||
|                 "stateMutability": "payable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [ | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" }, | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" }, | ||||
|                             { "internalType": "uint256", "name": "sellAmount", "type": "uint256" }, | ||||
|                             { | ||||
|                                 "components": [ | ||||
|                                     { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, | ||||
|                                     { "internalType": "uint256", "name": "sellAmount", "type": "uint256" }, | ||||
|                                     { "internalType": "bytes", "name": "data", "type": "bytes" } | ||||
|                                 ], | ||||
|                                 "internalType": "struct IMultiplexFeature.WrappedBatchCall[]", | ||||
|                                 "name": "calls", | ||||
|                                 "type": "tuple[]" | ||||
|                             } | ||||
|                         ], | ||||
|                         "internalType": "struct IMultiplexFeature.BatchFillData", | ||||
|                         "name": "fillData", | ||||
|                         "type": "tuple" | ||||
|                     }, | ||||
|                     { "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" } | ||||
|                 ], | ||||
|                 "name": "batchFill", | ||||
|                 "outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }], | ||||
|                 "stateMutability": "payable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [ | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, | ||||
|                             { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, | ||||
|                             { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, | ||||
|                             { "internalType": "uint128", "name": "takerTokenFeeAmount", "type": "uint128" }, | ||||
|                             { "internalType": "address", "name": "maker", "type": "address" }, | ||||
|                             { "internalType": "address", "name": "taker", "type": "address" }, | ||||
|                             { "internalType": "address", "name": "sender", "type": "address" }, | ||||
|                             { "internalType": "address", "name": "feeRecipient", "type": "address" }, | ||||
|                             { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, | ||||
|                             { "internalType": "uint64", "name": "expiry", "type": "uint64" }, | ||||
|                             { "internalType": "uint256", "name": "salt", "type": "uint256" } | ||||
|                         ], | ||||
|                         "internalType": "struct LibNativeOrder.LimitOrder[]", | ||||
|                         "name": "orders", | ||||
|                         "type": "tuple[]" | ||||
|                     }, | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { | ||||
|                                 "internalType": "enum LibSignature.SignatureType", | ||||
|                                 "name": "signatureType", | ||||
|                                 "type": "uint8" | ||||
|                             }, | ||||
|                             { "internalType": "uint8", "name": "v", "type": "uint8" }, | ||||
|                             { "internalType": "bytes32", "name": "r", "type": "bytes32" }, | ||||
|                             { "internalType": "bytes32", "name": "s", "type": "bytes32" } | ||||
|                         ], | ||||
|                         "internalType": "struct LibSignature.Signature[]", | ||||
|                         "name": "signatures", | ||||
|                         "type": "tuple[]" | ||||
|                     }, | ||||
|                     { "internalType": "uint128[]", "name": "takerTokenFillAmounts", "type": "uint128[]" }, | ||||
|                     { "internalType": "bool", "name": "revertIfIncomplete", "type": "bool" } | ||||
|                 ], | ||||
|                 "name": "batchFillLimitOrders", | ||||
|                 "outputs": [ | ||||
|                     { "internalType": "uint128[]", "name": "takerTokenFilledAmounts", "type": "uint128[]" }, | ||||
|                     { "internalType": "uint128[]", "name": "makerTokenFilledAmounts", "type": "uint128[]" } | ||||
|                 ], | ||||
|                 "stateMutability": "payable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [ | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" }, | ||||
|                             { "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" }, | ||||
|                             { "internalType": "uint128", "name": "makerAmount", "type": "uint128" }, | ||||
|                             { "internalType": "uint128", "name": "takerAmount", "type": "uint128" }, | ||||
|                             { "internalType": "address", "name": "maker", "type": "address" }, | ||||
|                             { "internalType": "address", "name": "taker", "type": "address" }, | ||||
|                             { "internalType": "address", "name": "txOrigin", "type": "address" }, | ||||
|                             { "internalType": "bytes32", "name": "pool", "type": "bytes32" }, | ||||
|                             { "internalType": "uint64", "name": "expiry", "type": "uint64" }, | ||||
|                             { "internalType": "uint256", "name": "salt", "type": "uint256" } | ||||
|                         ], | ||||
|                         "internalType": "struct LibNativeOrder.RfqOrder[]", | ||||
|                         "name": "orders", | ||||
|                         "type": "tuple[]" | ||||
|                     }, | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { | ||||
|                                 "internalType": "enum LibSignature.SignatureType", | ||||
|                                 "name": "signatureType", | ||||
|                                 "type": "uint8" | ||||
|                             }, | ||||
|                             { "internalType": "uint8", "name": "v", "type": "uint8" }, | ||||
|                             { "internalType": "bytes32", "name": "r", "type": "bytes32" }, | ||||
|                             { "internalType": "bytes32", "name": "s", "type": "bytes32" } | ||||
|                         ], | ||||
|                         "internalType": "struct LibSignature.Signature[]", | ||||
|                         "name": "signatures", | ||||
|                         "type": "tuple[]" | ||||
|                     }, | ||||
|                     { "internalType": "uint128[]", "name": "takerTokenFillAmounts", "type": "uint128[]" }, | ||||
|                     { "internalType": "bool", "name": "revertIfIncomplete", "type": "bool" } | ||||
|                 ], | ||||
|                 "name": "batchFillRfqOrders", | ||||
|                 "outputs": [ | ||||
|                     { "internalType": "uint128[]", "name": "takerTokenFilledAmounts", "type": "uint128[]" }, | ||||
|                     { "internalType": "uint128[]", "name": "makerTokenFilledAmounts", "type": "uint128[]" } | ||||
|                 ], | ||||
|                 "stateMutability": "nonpayable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [ | ||||
|                     { | ||||
| @@ -1221,6 +1337,33 @@ | ||||
|                 "stateMutability": "nonpayable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [ | ||||
|                     { | ||||
|                         "components": [ | ||||
|                             { "internalType": "address[]", "name": "tokens", "type": "address[]" }, | ||||
|                             { "internalType": "uint256", "name": "sellAmount", "type": "uint256" }, | ||||
|                             { | ||||
|                                 "components": [ | ||||
|                                     { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, | ||||
|                                     { "internalType": "bytes", "name": "data", "type": "bytes" } | ||||
|                                 ], | ||||
|                                 "internalType": "struct IMultiplexFeature.WrappedMultiHopCall[]", | ||||
|                                 "name": "calls", | ||||
|                                 "type": "tuple[]" | ||||
|                             } | ||||
|                         ], | ||||
|                         "internalType": "struct IMultiplexFeature.MultiHopFillData", | ||||
|                         "name": "fillData", | ||||
|                         "type": "tuple" | ||||
|                     }, | ||||
|                     { "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" } | ||||
|                 ], | ||||
|                 "name": "multiHopFill", | ||||
|                 "outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }], | ||||
|                 "stateMutability": "payable", | ||||
|                 "type": "function" | ||||
|             }, | ||||
|             { | ||||
|                 "inputs": [], | ||||
|                 "name": "owner", | ||||
| @@ -1402,6 +1545,40 @@ | ||||
|                     }, | ||||
|                     "returns": { "returnResults": "The ABI-encoded results of the underlying calls." } | ||||
|                 }, | ||||
|                 "batchFill((address,address,uint256,(bytes4,uint256,bytes)[]),uint256)": { | ||||
|                     "details": "Executes a batch of fills selling `fillData.inputToken`      for `fillData.outputToken` in sequence. Refer to the      internal variant `_batchFill` for the allowed nested      operations.", | ||||
|                     "params": { | ||||
|                         "fillData": "Encodes the input/output tokens, the sell        amount, and the nested operations for this batch fill.", | ||||
|                         "minBuyAmount": "The minimum amount of `fillData.outputToken`        to buy. Reverts if this amount is not met." | ||||
|                     }, | ||||
|                     "returns": { "outputTokenAmount": "The amount of the output token bought." } | ||||
|                 }, | ||||
|                 "batchFillLimitOrders((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)": { | ||||
|                     "details": "Fills multiple limit orders.", | ||||
|                     "params": { | ||||
|                         "orders": "Array of limit orders.", | ||||
|                         "revertIfIncomplete": "If true, reverts if this function fails to        fill the full fill amount for any individual order.", | ||||
|                         "signatures": "Array of signatures corresponding to each order.", | ||||
|                         "takerTokenFillAmounts": "Array of desired amounts to fill each order." | ||||
|                     }, | ||||
|                     "returns": { | ||||
|                         "makerTokenFilledAmounts": "Array of amounts filled, in maker token.", | ||||
|                         "takerTokenFilledAmounts": "Array of amounts filled, in taker token." | ||||
|                     } | ||||
|                 }, | ||||
|                 "batchFillRfqOrders((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)": { | ||||
|                     "details": "Fills multiple RFQ orders.", | ||||
|                     "params": { | ||||
|                         "orders": "Array of RFQ orders.", | ||||
|                         "revertIfIncomplete": "If true, reverts if this function fails to        fill the full fill amount for any individual order.", | ||||
|                         "signatures": "Array of signatures corresponding to each order.", | ||||
|                         "takerTokenFillAmounts": "Array of desired amounts to fill each order." | ||||
|                     }, | ||||
|                     "returns": { | ||||
|                         "makerTokenFilledAmounts": "Array of amounts filled, in maker token.", | ||||
|                         "takerTokenFilledAmounts": "Array of amounts filled, in taker token." | ||||
|                     } | ||||
|                 }, | ||||
|                 "batchGetLimitOrderRelevantStates((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])": { | ||||
|                     "details": "Batch version of `getLimitOrderRelevantState()`, without reverting.      Orders that would normally cause `getLimitOrderRelevantState()`      to revert will have empty results.", | ||||
|                     "params": { "orders": "The limit orders.", "signatures": "The order signatures." }, | ||||
| @@ -1600,6 +1777,14 @@ | ||||
|                         "target": "The migrator contract address." | ||||
|                     } | ||||
|                 }, | ||||
|                 "multiHopFill((address[],uint256,(bytes4,bytes)[]),uint256)": { | ||||
|                     "details": "Executes a sequence of fills \"hopping\" through the      path of tokens given by `fillData.tokens`. Refer to the      internal variant `_multiHopFill` for the allowed nested      operations.", | ||||
|                     "params": { | ||||
|                         "fillData": "Encodes the path of tokens, the sell amount,        and the nested operations for this multi-hop fill.", | ||||
|                         "minBuyAmount": "The minimum amount of the output token        to buy. Reverts if this amount is not met." | ||||
|                     }, | ||||
|                     "returns": { "outputTokenAmount": "The amount of the output token bought." } | ||||
|                 }, | ||||
|                 "owner()": { | ||||
|                     "details": "The owner of this contract.", | ||||
|                     "returns": { "ownerAddress": "The owner address." } | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "13.14.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Update IZeroExContract wrapper", | ||||
|                 "pr": 140 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "13.13.0", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -36,6 +36,7 @@ import * as ethers from 'ethers'; | ||||
| // tslint:enable:no-unused-variable | ||||
|  | ||||
| export type IZeroExEventArgs = | ||||
|     | IZeroExExpiredRfqOrderEventArgs | ||||
|     | IZeroExLimitOrderFilledEventArgs | ||||
|     | IZeroExLiquidityProviderSwapEventArgs | ||||
|     | IZeroExMetaTransactionExecutedEventArgs | ||||
| @@ -52,6 +53,7 @@ export type IZeroExEventArgs = | ||||
|     | IZeroExTransformerDeployerUpdatedEventArgs; | ||||
|  | ||||
| export enum IZeroExEvents { | ||||
|     ExpiredRfqOrder = 'ExpiredRfqOrder', | ||||
|     LimitOrderFilled = 'LimitOrderFilled', | ||||
|     LiquidityProviderSwap = 'LiquidityProviderSwap', | ||||
|     MetaTransactionExecuted = 'MetaTransactionExecuted', | ||||
| @@ -68,6 +70,12 @@ export enum IZeroExEvents { | ||||
|     TransformerDeployerUpdated = 'TransformerDeployerUpdated', | ||||
| } | ||||
|  | ||||
| export interface IZeroExExpiredRfqOrderEventArgs extends DecodedLogArgs { | ||||
|     orderHash: string; | ||||
|     maker: string; | ||||
|     expiry: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface IZeroExLimitOrderFilledEventArgs extends DecodedLogArgs { | ||||
|     orderHash: string; | ||||
|     maker: string; | ||||
| @@ -284,6 +292,29 @@ export class IZeroExContract extends BaseContract { | ||||
|      */ | ||||
|     public static ABI(): ContractAbi { | ||||
|         const abi = [ | ||||
|             { | ||||
|                 anonymous: false, | ||||
|                 inputs: [ | ||||
|                     { | ||||
|                         name: 'orderHash', | ||||
|                         type: 'bytes32', | ||||
|                         indexed: false, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'maker', | ||||
|                         type: 'address', | ||||
|                         indexed: false, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'expiry', | ||||
|                         type: 'uint64', | ||||
|                         indexed: false, | ||||
|                     }, | ||||
|                 ], | ||||
|                 name: 'ExpiredRfqOrder', | ||||
|                 outputs: [], | ||||
|                 type: 'event', | ||||
|             }, | ||||
|             { | ||||
|                 anonymous: false, | ||||
|                 inputs: [ | ||||
| @@ -1193,6 +1224,253 @@ export class IZeroExContract extends BaseContract { | ||||
|                 stateMutability: 'payable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [ | ||||
|                     { | ||||
|                         name: 'fillData', | ||||
|                         type: 'tuple', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'inputToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'outputToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'sellAmount', | ||||
|                                 type: 'uint256', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'calls', | ||||
|                                 type: 'tuple[]', | ||||
|                                 components: [ | ||||
|                                     { | ||||
|                                         name: 'selector', | ||||
|                                         type: 'bytes4', | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         name: 'sellAmount', | ||||
|                                         type: 'uint256', | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         name: 'data', | ||||
|                                         type: 'bytes', | ||||
|                                     }, | ||||
|                                 ], | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'minBuyAmount', | ||||
|                         type: 'uint256', | ||||
|                     }, | ||||
|                 ], | ||||
|                 name: 'batchFill', | ||||
|                 outputs: [ | ||||
|                     { | ||||
|                         name: 'outputTokenAmount', | ||||
|                         type: 'uint256', | ||||
|                     }, | ||||
|                 ], | ||||
|                 stateMutability: 'payable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [ | ||||
|                     { | ||||
|                         name: 'orders', | ||||
|                         type: 'tuple[]', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'makerToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'takerToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'makerAmount', | ||||
|                                 type: 'uint128', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'takerAmount', | ||||
|                                 type: 'uint128', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'takerTokenFeeAmount', | ||||
|                                 type: 'uint128', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'maker', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'taker', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'sender', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'feeRecipient', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'pool', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'expiry', | ||||
|                                 type: 'uint64', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'salt', | ||||
|                                 type: 'uint256', | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'signatures', | ||||
|                         type: 'tuple[]', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'signatureType', | ||||
|                                 type: 'uint8', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'v', | ||||
|                                 type: 'uint8', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'r', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 's', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'takerTokenFillAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'revertIfIncomplete', | ||||
|                         type: 'bool', | ||||
|                     }, | ||||
|                 ], | ||||
|                 name: 'batchFillLimitOrders', | ||||
|                 outputs: [ | ||||
|                     { | ||||
|                         name: 'takerTokenFilledAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'makerTokenFilledAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                 ], | ||||
|                 stateMutability: 'payable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [ | ||||
|                     { | ||||
|                         name: 'orders', | ||||
|                         type: 'tuple[]', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'makerToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'takerToken', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'makerAmount', | ||||
|                                 type: 'uint128', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'takerAmount', | ||||
|                                 type: 'uint128', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'maker', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'taker', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'txOrigin', | ||||
|                                 type: 'address', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'pool', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'expiry', | ||||
|                                 type: 'uint64', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'salt', | ||||
|                                 type: 'uint256', | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'signatures', | ||||
|                         type: 'tuple[]', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'signatureType', | ||||
|                                 type: 'uint8', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'v', | ||||
|                                 type: 'uint8', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'r', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 's', | ||||
|                                 type: 'bytes32', | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'takerTokenFillAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'revertIfIncomplete', | ||||
|                         type: 'bool', | ||||
|                     }, | ||||
|                 ], | ||||
|                 name: 'batchFillRfqOrders', | ||||
|                 outputs: [ | ||||
|                     { | ||||
|                         name: 'takerTokenFilledAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'makerTokenFilledAmounts', | ||||
|                         type: 'uint128[]', | ||||
|                     }, | ||||
|                 ], | ||||
|                 stateMutability: 'nonpayable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [ | ||||
|                     { | ||||
| @@ -2804,6 +3082,51 @@ export class IZeroExContract extends BaseContract { | ||||
|                 stateMutability: 'nonpayable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [ | ||||
|                     { | ||||
|                         name: 'fillData', | ||||
|                         type: 'tuple', | ||||
|                         components: [ | ||||
|                             { | ||||
|                                 name: 'tokens', | ||||
|                                 type: 'address[]', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'sellAmount', | ||||
|                                 type: 'uint256', | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'calls', | ||||
|                                 type: 'tuple[]', | ||||
|                                 components: [ | ||||
|                                     { | ||||
|                                         name: 'selector', | ||||
|                                         type: 'bytes4', | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         name: 'data', | ||||
|                                         type: 'bytes', | ||||
|                                     }, | ||||
|                                 ], | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'minBuyAmount', | ||||
|                         type: 'uint256', | ||||
|                     }, | ||||
|                 ], | ||||
|                 name: 'multiHopFill', | ||||
|                 outputs: [ | ||||
|                     { | ||||
|                         name: 'outputTokenAmount', | ||||
|                         type: 'uint256', | ||||
|                     }, | ||||
|                 ], | ||||
|                 stateMutability: 'payable', | ||||
|                 type: 'function', | ||||
|             }, | ||||
|             { | ||||
|                 inputs: [], | ||||
|                 name: 'owner', | ||||
| @@ -3705,6 +4028,240 @@ export class IZeroExContract extends BaseContract { | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * Executes a batch of fills selling `fillData.inputToken` | ||||
|      * for `fillData.outputToken` in sequence. Refer to the | ||||
|      * internal variant `_batchFill` for the allowed nested | ||||
|      * operations. | ||||
|      * @param fillData Encodes the input/output tokens, the sell        amount, and | ||||
|      *     the nested operations for this batch fill. | ||||
|      * @param minBuyAmount The minimum amount of `fillData.outputToken`        to | ||||
|      *     buy. Reverts if this amount is not met. | ||||
|      */ | ||||
|     public batchFill( | ||||
|         fillData: { | ||||
|             inputToken: string; | ||||
|             outputToken: string; | ||||
|             sellAmount: BigNumber; | ||||
|             calls: Array<{ selector: string; sellAmount: BigNumber; data: string }>; | ||||
|         }, | ||||
|         minBuyAmount: BigNumber, | ||||
|     ): ContractTxFunctionObj<BigNumber> { | ||||
|         const self = (this as any) as IZeroExContract; | ||||
|  | ||||
|         assert.isBigNumber('minBuyAmount', minBuyAmount); | ||||
|         const functionSignature = 'batchFill((address,address,uint256,(bytes4,uint256,bytes)[]),uint256)'; | ||||
|  | ||||
|         return { | ||||
|             async sendTransactionAsync( | ||||
|                 txData?: Partial<TxData> | undefined, | ||||
|                 opts: SendTransactionOpts = { shouldValidate: true }, | ||||
|             ): Promise<string> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...txData }, | ||||
|                     this.estimateGasAsync.bind(this), | ||||
|                 ); | ||||
|                 if (opts.shouldValidate !== false) { | ||||
|                     await this.callAsync(txDataWithDefaults); | ||||
|                 } | ||||
|                 return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             awaitTransactionSuccessAsync( | ||||
|                 txData?: Partial<TxData>, | ||||
|                 opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, | ||||
|             ): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> { | ||||
|                 return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); | ||||
|             }, | ||||
|             async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ | ||||
|                     data: this.getABIEncodedTransactionData(), | ||||
|                     ...txData, | ||||
|                 }); | ||||
|                 return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> { | ||||
|                 BaseContract._assertCallParams(callData, defaultBlock); | ||||
|                 const rawCallResult = await self._performCallAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...callData }, | ||||
|                     defaultBlock, | ||||
|                 ); | ||||
|                 const abiEncoder = self._lookupAbiEncoder(functionSignature); | ||||
|                 BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); | ||||
|                 return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult); | ||||
|             }, | ||||
|             getABIEncodedTransactionData(): string { | ||||
|                 return self._strictEncodeArguments(functionSignature, [fillData, minBuyAmount]); | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * Fills multiple limit orders. | ||||
|      * @param orders Array of limit orders. | ||||
|      * @param signatures Array of signatures corresponding to each order. | ||||
|      * @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|      * @param revertIfIncomplete If true, reverts if this function fails to | ||||
|      *     fill the full fill amount for any individual order. | ||||
|      */ | ||||
|     public batchFillLimitOrders( | ||||
|         orders: Array<{ | ||||
|             makerToken: string; | ||||
|             takerToken: string; | ||||
|             makerAmount: BigNumber; | ||||
|             takerAmount: BigNumber; | ||||
|             takerTokenFeeAmount: BigNumber; | ||||
|             maker: string; | ||||
|             taker: string; | ||||
|             sender: string; | ||||
|             feeRecipient: string; | ||||
|             pool: string; | ||||
|             expiry: BigNumber; | ||||
|             salt: BigNumber; | ||||
|         }>, | ||||
|         signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>, | ||||
|         takerTokenFillAmounts: BigNumber[], | ||||
|         revertIfIncomplete: boolean, | ||||
|     ): ContractTxFunctionObj<[BigNumber[], BigNumber[]]> { | ||||
|         const self = (this as any) as IZeroExContract; | ||||
|         assert.isArray('orders', orders); | ||||
|         assert.isArray('signatures', signatures); | ||||
|         assert.isArray('takerTokenFillAmounts', takerTokenFillAmounts); | ||||
|         assert.isBoolean('revertIfIncomplete', revertIfIncomplete); | ||||
|         const functionSignature = | ||||
|             'batchFillLimitOrders((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)'; | ||||
|  | ||||
|         return { | ||||
|             async sendTransactionAsync( | ||||
|                 txData?: Partial<TxData> | undefined, | ||||
|                 opts: SendTransactionOpts = { shouldValidate: true }, | ||||
|             ): Promise<string> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...txData }, | ||||
|                     this.estimateGasAsync.bind(this), | ||||
|                 ); | ||||
|                 if (opts.shouldValidate !== false) { | ||||
|                     await this.callAsync(txDataWithDefaults); | ||||
|                 } | ||||
|                 return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             awaitTransactionSuccessAsync( | ||||
|                 txData?: Partial<TxData>, | ||||
|                 opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, | ||||
|             ): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> { | ||||
|                 return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); | ||||
|             }, | ||||
|             async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ | ||||
|                     data: this.getABIEncodedTransactionData(), | ||||
|                     ...txData, | ||||
|                 }); | ||||
|                 return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             async callAsync( | ||||
|                 callData: Partial<CallData> = {}, | ||||
|                 defaultBlock?: BlockParam, | ||||
|             ): Promise<[BigNumber[], BigNumber[]]> { | ||||
|                 BaseContract._assertCallParams(callData, defaultBlock); | ||||
|                 const rawCallResult = await self._performCallAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...callData }, | ||||
|                     defaultBlock, | ||||
|                 ); | ||||
|                 const abiEncoder = self._lookupAbiEncoder(functionSignature); | ||||
|                 BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); | ||||
|                 return abiEncoder.strictDecodeReturnValue<[BigNumber[], BigNumber[]]>(rawCallResult); | ||||
|             }, | ||||
|             getABIEncodedTransactionData(): string { | ||||
|                 return self._strictEncodeArguments(functionSignature, [ | ||||
|                     orders, | ||||
|                     signatures, | ||||
|                     takerTokenFillAmounts, | ||||
|                     revertIfIncomplete, | ||||
|                 ]); | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * Fills multiple RFQ orders. | ||||
|      * @param orders Array of RFQ orders. | ||||
|      * @param signatures Array of signatures corresponding to each order. | ||||
|      * @param takerTokenFillAmounts Array of desired amounts to fill each order. | ||||
|      * @param revertIfIncomplete If true, reverts if this function fails to | ||||
|      *     fill the full fill amount for any individual order. | ||||
|      */ | ||||
|     public batchFillRfqOrders( | ||||
|         orders: Array<{ | ||||
|             makerToken: string; | ||||
|             takerToken: string; | ||||
|             makerAmount: BigNumber; | ||||
|             takerAmount: BigNumber; | ||||
|             maker: string; | ||||
|             taker: string; | ||||
|             txOrigin: string; | ||||
|             pool: string; | ||||
|             expiry: BigNumber; | ||||
|             salt: BigNumber; | ||||
|         }>, | ||||
|         signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>, | ||||
|         takerTokenFillAmounts: BigNumber[], | ||||
|         revertIfIncomplete: boolean, | ||||
|     ): ContractTxFunctionObj<[BigNumber[], BigNumber[]]> { | ||||
|         const self = (this as any) as IZeroExContract; | ||||
|         assert.isArray('orders', orders); | ||||
|         assert.isArray('signatures', signatures); | ||||
|         assert.isArray('takerTokenFillAmounts', takerTokenFillAmounts); | ||||
|         assert.isBoolean('revertIfIncomplete', revertIfIncomplete); | ||||
|         const functionSignature = | ||||
|             'batchFillRfqOrders((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)'; | ||||
|  | ||||
|         return { | ||||
|             async sendTransactionAsync( | ||||
|                 txData?: Partial<TxData> | undefined, | ||||
|                 opts: SendTransactionOpts = { shouldValidate: true }, | ||||
|             ): Promise<string> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...txData }, | ||||
|                     this.estimateGasAsync.bind(this), | ||||
|                 ); | ||||
|                 if (opts.shouldValidate !== false) { | ||||
|                     await this.callAsync(txDataWithDefaults); | ||||
|                 } | ||||
|                 return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             awaitTransactionSuccessAsync( | ||||
|                 txData?: Partial<TxData>, | ||||
|                 opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, | ||||
|             ): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> { | ||||
|                 return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); | ||||
|             }, | ||||
|             async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ | ||||
|                     data: this.getABIEncodedTransactionData(), | ||||
|                     ...txData, | ||||
|                 }); | ||||
|                 return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             async callAsync( | ||||
|                 callData: Partial<CallData> = {}, | ||||
|                 defaultBlock?: BlockParam, | ||||
|             ): Promise<[BigNumber[], BigNumber[]]> { | ||||
|                 BaseContract._assertCallParams(callData, defaultBlock); | ||||
|                 const rawCallResult = await self._performCallAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...callData }, | ||||
|                     defaultBlock, | ||||
|                 ); | ||||
|                 const abiEncoder = self._lookupAbiEncoder(functionSignature); | ||||
|                 BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); | ||||
|                 return abiEncoder.strictDecodeReturnValue<[BigNumber[], BigNumber[]]>(rawCallResult); | ||||
|             }, | ||||
|             getABIEncodedTransactionData(): string { | ||||
|                 return self._strictEncodeArguments(functionSignature, [ | ||||
|                     orders, | ||||
|                     signatures, | ||||
|                     takerTokenFillAmounts, | ||||
|                     revertIfIncomplete, | ||||
|                 ]); | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * Batch version of `getLimitOrderRelevantState()`, without reverting. | ||||
|      * Orders that would normally cause `getLimitOrderRelevantState()` | ||||
| @@ -5679,6 +6236,67 @@ export class IZeroExContract extends BaseContract { | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * Executes a sequence of fills "hopping" through the | ||||
|      * path of tokens given by `fillData.tokens`. Refer to the | ||||
|      * internal variant `_multiHopFill` for the allowed nested | ||||
|      * operations. | ||||
|      * @param fillData Encodes the path of tokens, the sell amount,        and the | ||||
|      *     nested operations for this multi-hop fill. | ||||
|      * @param minBuyAmount The minimum amount of the output token        to buy. | ||||
|      *     Reverts if this amount is not met. | ||||
|      */ | ||||
|     public multiHopFill( | ||||
|         fillData: { tokens: string[]; sellAmount: BigNumber; calls: Array<{ selector: string; data: string }> }, | ||||
|         minBuyAmount: BigNumber, | ||||
|     ): ContractTxFunctionObj<BigNumber> { | ||||
|         const self = (this as any) as IZeroExContract; | ||||
|  | ||||
|         assert.isBigNumber('minBuyAmount', minBuyAmount); | ||||
|         const functionSignature = 'multiHopFill((address[],uint256,(bytes4,bytes)[]),uint256)'; | ||||
|  | ||||
|         return { | ||||
|             async sendTransactionAsync( | ||||
|                 txData?: Partial<TxData> | undefined, | ||||
|                 opts: SendTransactionOpts = { shouldValidate: true }, | ||||
|             ): Promise<string> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...txData }, | ||||
|                     this.estimateGasAsync.bind(this), | ||||
|                 ); | ||||
|                 if (opts.shouldValidate !== false) { | ||||
|                     await this.callAsync(txDataWithDefaults); | ||||
|                 } | ||||
|                 return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             awaitTransactionSuccessAsync( | ||||
|                 txData?: Partial<TxData>, | ||||
|                 opts: AwaitTransactionSuccessOpts = { shouldValidate: true }, | ||||
|             ): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> { | ||||
|                 return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts); | ||||
|             }, | ||||
|             async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> { | ||||
|                 const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({ | ||||
|                     data: this.getABIEncodedTransactionData(), | ||||
|                     ...txData, | ||||
|                 }); | ||||
|                 return self._web3Wrapper.estimateGasAsync(txDataWithDefaults); | ||||
|             }, | ||||
|             async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> { | ||||
|                 BaseContract._assertCallParams(callData, defaultBlock); | ||||
|                 const rawCallResult = await self._performCallAsync( | ||||
|                     { data: this.getABIEncodedTransactionData(), ...callData }, | ||||
|                     defaultBlock, | ||||
|                 ); | ||||
|                 const abiEncoder = self._lookupAbiEncoder(functionSignature); | ||||
|                 BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder); | ||||
|                 return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult); | ||||
|             }, | ||||
|             getABIEncodedTransactionData(): string { | ||||
|                 return self._strictEncodeArguments(functionSignature, [fillData, minBuyAmount]); | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * The owner of this contract. | ||||
|      */ | ||||
|   | ||||
| @@ -125,6 +125,7 @@ export { | ||||
|     IZeroExContract, | ||||
|     IZeroExEventArgs, | ||||
|     IZeroExEvents, | ||||
|     IZeroExExpiredRfqOrderEventArgs, | ||||
|     IZeroExLiquidityProviderSwapEventArgs, | ||||
|     IZeroExMetaTransactionExecutedEventArgs, | ||||
|     IZeroExMigratedEventArgs, | ||||
|   | ||||
| @@ -110,6 +110,20 @@ export class OnlyOrderMakerAllowed extends RevertError { | ||||
|     } | ||||
| } | ||||
|  | ||||
| export class BatchFillIncompleteError extends RevertError { | ||||
|     constructor(orderHash?: string, takerTokenFilledAmount?: Numberish, takerTokenFillAmount?: Numberish) { | ||||
|         super( | ||||
|             'BatchFillIncompleteError', | ||||
|             'BatchFillIncompleteError(bytes32 orderHash, uint256 takerTokenFilledAmount, uint256 takerTokenFillAmount)', | ||||
|             { | ||||
|                 orderHash, | ||||
|                 takerTokenFilledAmount, | ||||
|                 takerTokenFillAmount, | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const types = [ | ||||
|     ProtocolFeeRefundFailed, | ||||
|     OrderNotFillableByOriginError, | ||||
| @@ -120,6 +134,7 @@ const types = [ | ||||
|     CancelSaltTooLowError, | ||||
|     FillOrKillFailedError, | ||||
|     OnlyOrderMakerAllowed, | ||||
|     BatchFillIncompleteError, | ||||
| ]; | ||||
|  | ||||
| // Register the types we've defined. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user