Compare commits
	
		
			6 Commits
		
	
	
		
			protocol@e
			...
			poc/rust-r
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 292ce6ea84 | ||
|  | 7972c2ce4e | ||
|  | 173d4ce648 | ||
|  | 59542f0585 | ||
|  | 446ef9660e | ||
|  | 9de1f0263a | 
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "0.27.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "Refactor Mixins which use WETH to also have an Internal variant, allowing WETH to be passed in for SwapRevertSampler", | ||||
|                 "pr": 245 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "version": "0.26.0", | ||||
|         "changes": [ | ||||
|   | ||||
| @@ -39,6 +39,13 @@ interface IBancorNetwork { | ||||
|         external | ||||
|         payable | ||||
|         returns (uint256); | ||||
|     function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory); | ||||
|     function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256); | ||||
| } | ||||
|  | ||||
| interface IBancorRegistry { | ||||
|     function getAddress(bytes32 _contractName) external view returns (address); | ||||
|     function BANCOR_NETWORK() external view returns (bytes32); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -62,6 +69,18 @@ contract MixinBancor { | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         return _tradeBancorInternal(WETH, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeBancorInternal( | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         // Decode the bridge data. | ||||
|         IBancorNetwork bancorNetworkAddress; | ||||
| @@ -79,7 +98,7 @@ contract MixinBancor { | ||||
|         require(path.length >= 2, "MixinBancor/PATH_LENGTH_MUST_BE_AT_LEAST_TWO"); | ||||
|         require( | ||||
|             path[path.length - 1] == buyToken || | ||||
|             (path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == WETH), | ||||
|             (path[path.length - 1] == BANCOR_ETH_ADDRESS && buyToken == weth), | ||||
|             "MixinBancor/LAST_ELEMENT_OF_PATH_MUST_MATCH_OUTPUT_TOKEN" | ||||
|         ); | ||||
|  | ||||
| @@ -88,7 +107,7 @@ contract MixinBancor { | ||||
|         // The Bancor path will have ETH as the 0xeee address | ||||
|         // Bancor expects to be paid in ETH not WETH | ||||
|         if (path[0] == BANCOR_ETH_ADDRESS) { | ||||
|             WETH.withdraw(sellAmount); | ||||
|             weth.withdraw(sellAmount); | ||||
|             payableAmount = sellAmount; | ||||
|         } else { | ||||
|             // Grant an allowance to the Bancor Network. | ||||
| @@ -109,7 +128,7 @@ contract MixinBancor { | ||||
|             0 // affiliateFee; no fee paid | ||||
|         ); | ||||
|         if (path[path.length - 1] == BANCOR_ETH_ADDRESS) { | ||||
|             WETH.deposit{value: boughtAmount}(); | ||||
|             weth.deposit{value: boughtAmount}(); | ||||
|         } | ||||
|  | ||||
|         return boughtAmount; | ||||
|   | ||||
| @@ -57,13 +57,26 @@ contract MixinCurve { | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         return _tradeCurveInternal(WETH, sellToken, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeCurveInternal( | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         // Decode the bridge data to get the Curve metadata. | ||||
|         CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData)); | ||||
|         uint256 payableAmount; | ||||
|         if (sellToken == WETH) { | ||||
|         if (sellToken == weth) { | ||||
|             payableAmount = sellAmount; | ||||
|             WETH.withdraw(sellAmount); | ||||
|             weth.withdraw(sellAmount); | ||||
|         } else { | ||||
|             sellToken.approveIfBelow(data.curveAddress, sellAmount); | ||||
|         } | ||||
| @@ -83,9 +96,9 @@ contract MixinCurve { | ||||
|             resultData.rrevert(); | ||||
|         } | ||||
|  | ||||
|         if (buyToken == WETH) { | ||||
|         if (buyToken == weth) { | ||||
|             boughtAmount = address(this).balance; | ||||
|             WETH.deposit{ value: boughtAmount }(); | ||||
|             weth.deposit{ value: boughtAmount }(); | ||||
|         } | ||||
|  | ||||
|         return buyToken.balanceOf(address(this)).safeSub(beforeBalance); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
|   | ||||
| @@ -52,6 +52,7 @@ interface IKyberNetworkProxy { | ||||
|         external | ||||
|         payable | ||||
|         returns (uint256 boughtAmount); | ||||
|  | ||||
| } | ||||
|  | ||||
| contract MixinKyber { | ||||
| @@ -78,12 +79,26 @@ contract MixinKyber { | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         return _tradeKyberInternal(KYBER_ETH_ADDRESS, WETH, sellToken, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeKyberInternal( | ||||
|         IERC20TokenV06 kyberEthAddress, | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         (IKyberNetworkProxy kyber, bytes memory hint) = | ||||
|             abi.decode(bridgeData, (IKyberNetworkProxy, bytes)); | ||||
|  | ||||
|         uint256 payableAmount = 0; | ||||
|         if (sellToken != WETH) { | ||||
|         if (sellToken != weth) { | ||||
|             // If the input token is not WETH, grant an allowance to the exchange | ||||
|             // to spend them. | ||||
|             sellToken.approveIfBelow( | ||||
| @@ -93,18 +108,18 @@ contract MixinKyber { | ||||
|         } else { | ||||
|             // If the input token is WETH, unwrap it and attach it to the call. | ||||
|             payableAmount = sellAmount; | ||||
|             WETH.withdraw(payableAmount); | ||||
|             weth.withdraw(payableAmount); | ||||
|         } | ||||
|  | ||||
|         // Try to sell all of this contract's input token balance through | ||||
|         // `KyberNetworkProxy.trade()`. | ||||
|         boughtAmount = kyber.tradeWithHint{ value: payableAmount }( | ||||
|             // Input token. | ||||
|             sellToken == WETH ? KYBER_ETH_ADDRESS : sellToken, | ||||
|             sellToken == weth ? kyberEthAddress : sellToken, | ||||
|             // Sell amount. | ||||
|             sellAmount, | ||||
|             // Output token. | ||||
|             buyToken == WETH ? KYBER_ETH_ADDRESS : buyToken, | ||||
|             buyToken == weth ? kyberEthAddress : buyToken, | ||||
|             // Transfer to this contract | ||||
|             address(uint160(address(this))), | ||||
|             // Buy as much as possible. | ||||
| @@ -116,8 +131,8 @@ contract MixinKyber { | ||||
|             hint | ||||
|         ); | ||||
|         // If receving ETH, wrap it to WETH. | ||||
|         if (buyToken == WETH) { | ||||
|             WETH.deposit{ value: boughtAmount }(); | ||||
|         if (buyToken == weth) { | ||||
|             weth.deposit{ value: boughtAmount }(); | ||||
|         } | ||||
|         return boughtAmount; | ||||
|     } | ||||
|   | ||||
| @@ -30,6 +30,8 @@ import "../IBridgeAdapter.sol"; | ||||
| */ | ||||
| interface IKyberDmmRouter { | ||||
|  | ||||
|     function factory() external view returns (address); | ||||
|  | ||||
|     /// @dev Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path. | ||||
|     ///      The first element of path is the input token, the last is the output token, and any intermediate elements represent | ||||
|     ///      intermediate pairs to trade through (if, for example, a direct pair does not exist). | ||||
|   | ||||
| @@ -58,10 +58,23 @@ contract MixinLido { | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         return _tradeLidoInternal(WETH, sellToken, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeLidoInternal( | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         (ILido lido) = abi.decode(bridgeData, (ILido)); | ||||
|         if (address(sellToken) == address(WETH) && address(buyToken) == address(lido)) { | ||||
|             WETH.withdraw(sellAmount); | ||||
|         if (address(sellToken) == address(weth) && address(buyToken) == address(lido)) { | ||||
|             weth.withdraw(sellAmount); | ||||
|             boughtAmount = lido.getPooledEthByShares(lido.submit{ value: sellAmount}(address(0))); | ||||
|         } else { | ||||
|             revert("MixinLido/UNSUPPORTED_TOKEN_PAIR"); | ||||
|   | ||||
| @@ -66,12 +66,26 @@ contract MixinMooniswap { | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|  | ||||
|         return _tradeMooniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeMooniswapInternal( | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         (IMooniswapPool pool) = abi.decode(bridgeData, (IMooniswapPool)); | ||||
|  | ||||
|         // Convert WETH to ETH. | ||||
|         uint256 ethValue = 0; | ||||
|         if (sellToken == WETH) { | ||||
|             WETH.withdraw(sellAmount); | ||||
|         if (sellToken == weth) { | ||||
|             weth.withdraw(sellAmount); | ||||
|             ethValue = sellAmount; | ||||
|         } else { | ||||
|             // Grant the pool an allowance. | ||||
| @@ -82,16 +96,16 @@ contract MixinMooniswap { | ||||
|         } | ||||
|  | ||||
|         boughtAmount = pool.swap{value: ethValue}( | ||||
|             sellToken == WETH ? IERC20TokenV06(0) : sellToken, | ||||
|             buyToken == WETH ? IERC20TokenV06(0) : buyToken, | ||||
|             sellToken == weth ? IERC20TokenV06(0) : sellToken, | ||||
|             buyToken == weth ? IERC20TokenV06(0) : buyToken, | ||||
|             sellAmount, | ||||
|             1, | ||||
|             address(0) | ||||
|         ); | ||||
|  | ||||
|         // Wrap ETH to WETH. | ||||
|         if (buyToken == WETH) { | ||||
|             WETH.deposit{value:boughtAmount}(); | ||||
|         if (buyToken == weth) { | ||||
|             weth.deposit{value:boughtAmount}(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -124,21 +124,35 @@ contract MixinUniswap { | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         return _tradeUniswapInternal(WETH, sellToken, buyToken, sellAmount, bridgeData); | ||||
|     } | ||||
|  | ||||
|     function _tradeUniswapInternal( | ||||
|         IEtherTokenV06 weth, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken, | ||||
|         uint256 sellAmount, | ||||
|         bytes memory bridgeData | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         IUniswapExchangeFactory exchangeFactory = | ||||
|             abi.decode(bridgeData, (IUniswapExchangeFactory)); | ||||
|  | ||||
|         // Get the exchange for the token pair. | ||||
|         IUniswapExchange exchange = _getUniswapExchangeForTokenPair( | ||||
|             weth, | ||||
|             exchangeFactory, | ||||
|             sellToken, | ||||
|             buyToken | ||||
|         ); | ||||
|  | ||||
|         // Convert from WETH to a token. | ||||
|         if (sellToken == WETH) { | ||||
|         if (sellToken == weth) { | ||||
|             // Unwrap the WETH. | ||||
|             WETH.withdraw(sellAmount); | ||||
|             weth.withdraw(sellAmount); | ||||
|             // Buy as much of `buyToken` token with ETH as possible | ||||
|             boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }( | ||||
|                 // Minimum buy amount. | ||||
| @@ -150,7 +164,7 @@ contract MixinUniswap { | ||||
|             ); | ||||
|  | ||||
|         // Convert from a token to WETH. | ||||
|         } else if (buyToken == WETH) { | ||||
|         } else if (buyToken == weth) { | ||||
|             // Grant the exchange an allowance. | ||||
|             sellToken.approveIfBelow( | ||||
|                 address(exchange), | ||||
| @@ -166,7 +180,7 @@ contract MixinUniswap { | ||||
|                 block.timestamp | ||||
|             ); | ||||
|             // Wrap the ETH. | ||||
|             WETH.deposit{ value: boughtAmount }(); | ||||
|             weth.deposit{ value: boughtAmount }(); | ||||
|         // Convert from one token to another. | ||||
|         } else { | ||||
|             // Grant the exchange an allowance. | ||||
| @@ -200,6 +214,7 @@ contract MixinUniswap { | ||||
|     /// @param buyToken The address of the token we are converting to. | ||||
|     /// @return exchange The uniswap exchange. | ||||
|     function _getUniswapExchangeForTokenPair( | ||||
|         IEtherTokenV06 weth, | ||||
|         IUniswapExchangeFactory exchangeFactory, | ||||
|         IERC20TokenV06 sellToken, | ||||
|         IERC20TokenV06 buyToken | ||||
| @@ -209,7 +224,7 @@ contract MixinUniswap { | ||||
|         returns (IUniswapExchange exchange) | ||||
|     { | ||||
|         // Whichever isn't WETH is the exchange token. | ||||
|         exchange = sellToken == WETH | ||||
|         exchange = sellToken == weth | ||||
|             ? exchangeFactory.getExchange(buyToken) | ||||
|             : exchangeFactory.getExchange(sellToken); | ||||
|         require(address(exchange) != address(0), "MixinUniswap/NO_EXCHANGE"); | ||||
|   | ||||
| @@ -1,4 +1,13 @@ | ||||
| [ | ||||
|     { | ||||
|         "version": "7.0.0", | ||||
|         "changes": [ | ||||
|             { | ||||
|                 "note": "SwapRevertSampler. Refactored sampler to use exchange functions. Remove gas schedule.", | ||||
|                 "pr": 245 | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|         "timestamp": 1624562704, | ||||
|         "version": "6.18.2", | ||||
|   | ||||
| @@ -1,144 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
|  | ||||
|  | ||||
| contract ApproximateBuys { | ||||
|  | ||||
|     /// @dev Information computing buy quotes for sources that do not have native | ||||
|     ///      buy quote support. | ||||
|     struct ApproximateBuyQuoteOpts { | ||||
|         // Arbitrary maker token data to pass to `getSellQuoteCallback`. | ||||
|         bytes makerTokenData; | ||||
|         // Arbitrary taker token data to pass to `getSellQuoteCallback`. | ||||
|         bytes takerTokenData; | ||||
|         // Callback to retrieve a sell quote. | ||||
|         function (bytes memory, bytes memory, uint256) | ||||
|             internal | ||||
|             view | ||||
|             returns (uint256) getSellQuoteCallback; | ||||
|     } | ||||
|  | ||||
|     uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4; | ||||
|     /// @dev Maximum approximate (positive) error rate when approximating a buy quote. | ||||
|     uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4; | ||||
|     /// @dev Maximum iterations to perform when approximating a buy quote. | ||||
|     uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5; | ||||
|  | ||||
|     function _sampleApproximateBuys( | ||||
|         ApproximateBuyQuoteOpts memory opts, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         takerTokenAmounts = new uint256[](makerTokenAmounts.length); | ||||
|         if (makerTokenAmounts.length == 0) { | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         uint256 sellAmount = opts.getSellQuoteCallback( | ||||
|             opts.makerTokenData, | ||||
|             opts.takerTokenData, | ||||
|             makerTokenAmounts[0] | ||||
|         ); | ||||
|         if (sellAmount == 0) { | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         uint256 buyAmount = opts.getSellQuoteCallback( | ||||
|             opts.takerTokenData, | ||||
|             opts.makerTokenData, | ||||
|             sellAmount | ||||
|         ); | ||||
|         if (buyAmount == 0) { | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokenAmounts.length; i++) { | ||||
|             for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) { | ||||
|                 // adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER | ||||
|                 sellAmount = _safeGetPartialAmountCeil( | ||||
|                     makerTokenAmounts[i], | ||||
|                     buyAmount, | ||||
|                     sellAmount | ||||
|                 ); | ||||
|                 if (sellAmount == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 sellAmount = _safeGetPartialAmountCeil( | ||||
|                     (ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS), | ||||
|                     ONE_HUNDED_PERCENT_BPS, | ||||
|                     sellAmount | ||||
|                 ); | ||||
|                 if (sellAmount == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 uint256 _buyAmount = opts.getSellQuoteCallback( | ||||
|                     opts.takerTokenData, | ||||
|                     opts.makerTokenData, | ||||
|                     sellAmount | ||||
|                 ); | ||||
|                 if (_buyAmount == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 // We re-use buyAmount next iteration, only assign if it is | ||||
|                 // non zero | ||||
|                 buyAmount = _buyAmount; | ||||
|                 // If we've reached our goal, exit early | ||||
|                 if (buyAmount >= makerTokenAmounts[i]) { | ||||
|                     uint256 eps = | ||||
|                         (buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS / | ||||
|                         makerTokenAmounts[i]; | ||||
|                     if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             // We do our best to close in on the requested amount, but we can either over buy or under buy and exit | ||||
|             // if we hit a max iteration limit | ||||
|             // We scale the sell amount to get the approximate target | ||||
|             takerTokenAmounts[i] = _safeGetPartialAmountCeil( | ||||
|                 makerTokenAmounts[i], | ||||
|                 buyAmount, | ||||
|                 sellAmount | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _safeGetPartialAmountCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         if (numerator == 0 || target == 0 || denominator == 0) return 0; | ||||
|         uint256 c = numerator * target; | ||||
|         if (c / numerator != target) return 0; | ||||
|         return (c + (denominator - 1)) / denominator; | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,26 +20,29 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IBalancer.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancer.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract BalancerSampler is | ||||
|     MixinBalancer, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
| contract BalancerSampler { | ||||
|  | ||||
|     /// @dev Base gas limit for Balancer calls. | ||||
|     uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k | ||||
|  | ||||
|     // Balancer math constants | ||||
|     // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol | ||||
|     uint256 constant private BONE = 10 ** 18; | ||||
|     uint256 constant private MAX_IN_RATIO = BONE / 2; | ||||
|     uint256 constant private MAX_OUT_RATIO = (BONE / 3) + 1 wei; | ||||
|  | ||||
|     struct BalancerState { | ||||
|         uint256 takerTokenBalance; | ||||
|         uint256 makerTokenBalance; | ||||
|         uint256 takerTokenWeight; | ||||
|         uint256 makerTokenWeight; | ||||
|         uint256 swapFee; | ||||
|     function sampleSwapFromBalancer( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeBalancer( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Balancer. | ||||
| @@ -47,6 +50,7 @@ contract BalancerSampler { | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromBalancer( | ||||
| @@ -56,52 +60,17 @@ contract BalancerSampler { | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         IBalancer pool = IBalancer(poolAddress); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) { | ||||
|             return makerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         BalancerState memory poolState; | ||||
|         poolState.takerTokenBalance = pool.getBalance(takerToken); | ||||
|         poolState.makerTokenBalance = pool.getBalance(makerToken); | ||||
|         poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken); | ||||
|         poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken); | ||||
|         poolState.swapFee = pool.getSwapFee(); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             // Handles this revert scenario: | ||||
|             // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443 | ||||
|             if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) { | ||||
|                 break; | ||||
|             } | ||||
|             try | ||||
|                 pool.calcOutGivenIn | ||||
|                     {gas: BALANCER_CALL_GAS} | ||||
|                     ( | ||||
|                         poolState.takerTokenBalance, | ||||
|                         poolState.takerTokenWeight, | ||||
|                         poolState.makerTokenBalance, | ||||
|                         poolState.makerTokenWeight, | ||||
|                         takerTokenAmounts[i], | ||||
|                         poolState.swapFee | ||||
|                     ) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(poolAddress), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromBalancer | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Balancer. | ||||
| @@ -109,6 +78,7 @@ contract BalancerSampler { | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromBalancer( | ||||
| @@ -118,74 +88,18 @@ contract BalancerSampler { | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         IBalancer pool = IBalancer(poolAddress); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) { | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(poolAddress), | ||||
|                 buyTokenData: abi.encode(poolAddress), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromBalancer | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|  | ||||
|         BalancerState memory poolState; | ||||
|         poolState.takerTokenBalance = pool.getBalance(takerToken); | ||||
|         poolState.makerTokenBalance = pool.getBalance(makerToken); | ||||
|         poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken); | ||||
|         poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken); | ||||
|         poolState.swapFee = pool.getSwapFee(); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             // Handles this revert scenario: | ||||
|             // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505 | ||||
|             if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) { | ||||
|                 break; | ||||
|             } | ||||
|             try | ||||
|                 pool.calcInGivenOut | ||||
|                     {gas: BALANCER_CALL_GAS} | ||||
|                     ( | ||||
|                         poolState.takerTokenBalance, | ||||
|                         poolState.takerTokenWeight, | ||||
|                         poolState.makerTokenBalance, | ||||
|                         poolState.makerTokenWeight, | ||||
|                         makerTokenAmounts[i], | ||||
|                         poolState.swapFee | ||||
|                     ) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 takerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (takerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Hacked version of Balancer's `bmul` function, returning 0 instead | ||||
|     ///      of reverting. | ||||
|     ///      https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73 | ||||
|     /// @param a The first operand. | ||||
|     /// @param b The second operand. | ||||
|     /// @param c The result of the multiplication, or 0 if `bmul` would've reverted. | ||||
|     function _bmul(uint256 a, uint256 b) | ||||
|         private | ||||
|         pure | ||||
|         returns (uint256 c) | ||||
|     { | ||||
|         uint c0 = a * b; | ||||
|         if (a != 0 && c0 / a != b) { | ||||
|             return 0; | ||||
|         } | ||||
|         uint c1 = c0 + (BONE / 2); | ||||
|         if (c1 < c0) { | ||||
|             return 0; | ||||
|         } | ||||
|         uint c2 = c1 / BONE; | ||||
|         return c2; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,44 +20,29 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancerV2.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| /// @dev Minimal Balancer V2 Vault interface | ||||
| ///      for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol | ||||
| interface IBalancerV2Vault { | ||||
|     enum SwapKind { GIVEN_IN, GIVEN_OUT } | ||||
| contract BalancerV2Sampler is | ||||
|     MixinBalancerV2, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     struct BatchSwapStep { | ||||
|         bytes32 poolId; | ||||
|         uint256 assetInIndex; | ||||
|         uint256 assetOutIndex; | ||||
|         uint256 amount; | ||||
|         bytes userData; | ||||
|     } | ||||
|  | ||||
|     struct FundManagement { | ||||
|         address sender; | ||||
|         bool fromInternalBalance; | ||||
|         address payable recipient; | ||||
|         bool toInternalBalance; | ||||
|     } | ||||
|  | ||||
|     function queryBatchSwap( | ||||
|         SwapKind kind, | ||||
|         BatchSwapStep[] calldata swaps, | ||||
|         IAsset[] calldata assets, | ||||
|         FundManagement calldata funds | ||||
|     ) external returns (int256[] memory assetDeltas); | ||||
| } | ||||
| interface IAsset { | ||||
|     // solhint-disable-previous-line no-empty-blocks | ||||
| } | ||||
|  | ||||
| contract BalancerV2Sampler is SamplerUtils { | ||||
|  | ||||
|     struct BalancerV2PoolInfo { | ||||
|         bytes32 poolId; | ||||
|         address vault; | ||||
|     function sampleSwapFromBalancerV2( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeBalancerV2( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Balancer V2. | ||||
| @@ -65,48 +50,27 @@ contract BalancerV2Sampler is SamplerUtils { | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromBalancerV2( | ||||
|         BalancerV2PoolInfo memory poolInfo, | ||||
|         BalancerV2BridgeData memory poolInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); | ||||
|         IAsset[] memory swapAssets = new IAsset[](2); | ||||
|         swapAssets[0] = IAsset(takerToken); | ||||
|         swapAssets[1] = IAsset(makerToken); | ||||
|  | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         IBalancerV2Vault.FundManagement memory swapFunds = | ||||
|             _createSwapFunds(); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             IBalancerV2Vault.BatchSwapStep[] memory swapSteps = | ||||
|                 _createSwapSteps(poolInfo, takerTokenAmounts[i]); | ||||
|  | ||||
|             try | ||||
|                 // For sells we specify the takerToken which is what the vault will receive from the trade | ||||
|                 vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds) | ||||
|             // amounts represent pool balance deltas from the swap (incoming balance, outgoing balance) | ||||
|             returns (int256[] memory amounts) { | ||||
|                 // Outgoing balance is negative so we need to flip the sign | ||||
|                 int256 amountOutFromPool = amounts[1] * -1; | ||||
|                 if (amountOutFromPool <= 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 makerTokenAmounts[i] = uint256(amountOutFromPool); | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(poolInfo), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromBalancerV2 | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Balancer V2. | ||||
| @@ -114,76 +78,27 @@ contract BalancerV2Sampler is SamplerUtils { | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromBalancerV2( | ||||
|         BalancerV2PoolInfo memory poolInfo, | ||||
|         BalancerV2BridgeData memory poolInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); | ||||
|         IAsset[] memory swapAssets = new IAsset[](2); | ||||
|         swapAssets[0] = IAsset(takerToken); | ||||
|         swapAssets[1] = IAsset(makerToken); | ||||
|  | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         IBalancerV2Vault.FundManagement memory swapFunds = | ||||
|             _createSwapFunds(); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             IBalancerV2Vault.BatchSwapStep[] memory swapSteps = | ||||
|                 _createSwapSteps(poolInfo, makerTokenAmounts[i]); | ||||
|  | ||||
|             try | ||||
|                 // For buys we specify the makerToken which is what taker will receive from the trade | ||||
|                 vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds) | ||||
|             returns (int256[] memory amounts) { | ||||
|                 int256 amountIntoPool = amounts[0]; | ||||
|                 if (amountIntoPool <= 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 takerTokenAmounts[i] = uint256(amountIntoPool); | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _createSwapSteps( | ||||
|         BalancerV2PoolInfo memory poolInfo, | ||||
|         uint256 amount | ||||
|     ) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) { | ||||
|         IBalancerV2Vault.BatchSwapStep[] memory swapSteps = | ||||
|             new IBalancerV2Vault.BatchSwapStep[](1); | ||||
|         swapSteps[0] = IBalancerV2Vault.BatchSwapStep({ | ||||
|             poolId: poolInfo.poolId, | ||||
|             assetInIndex: 0, | ||||
|             assetOutIndex: 1, | ||||
|             amount: amount, | ||||
|             userData: "" | ||||
|         }); | ||||
|  | ||||
|         return swapSteps; | ||||
|     } | ||||
|  | ||||
|     function _createSwapFunds() | ||||
|         private | ||||
|         view | ||||
|         returns (IBalancerV2Vault.FundManagement memory) | ||||
|     { | ||||
|         return | ||||
|             IBalancerV2Vault.FundManagement({ | ||||
|                 sender: address(this), | ||||
|                 fromInternalBalance: false, | ||||
|                 recipient: payable(address(this)), | ||||
|                 toInternalBalance: false | ||||
|             }); | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(poolInfo), | ||||
|                 buyTokenData: abi.encode(poolInfo), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromBalancerV2 | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,11 +20,21 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IBancor.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinBancor.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract CompilerHack {} | ||||
|  | ||||
| contract BancorSampler is CompilerHack { | ||||
| contract BancorSampler is | ||||
|     CompilerHack, | ||||
|     MixinBancor, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinBancor(weth) | ||||
|     { } | ||||
|  | ||||
|     /// @dev Base gas limit for Bancor calls. | ||||
|     uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k | ||||
| @@ -34,6 +44,23 @@ contract BancorSampler is CompilerHack { | ||||
|         address[][] paths; | ||||
|     } | ||||
|  | ||||
|     function sampleSwapFromBancor( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeBancorInternal( | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Bancor. | ||||
|     /// @param opts BancorSamplerOpts The Bancor registry contract address and paths | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
| @@ -41,6 +68,7 @@ contract BancorSampler is CompilerHack { | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return bancorNetwork the Bancor Network address | ||||
|     /// @return path the selected conversion path from bancor | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromBancor( | ||||
| @@ -50,34 +78,30 @@ contract BancorSampler is CompilerHack { | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts) | ||||
|         returns ( | ||||
|             address bancorNetwork, | ||||
|             address[] memory path, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         if (opts.paths.length == 0) { | ||||
|             return (bancorNetwork, path, makerTokenAmounts); | ||||
|             return (bancorNetwork, path, gasUsed, makerTokenAmounts); | ||||
|         } | ||||
|         (bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts); | ||||
|         makerTokenAmounts = new uint256[](takerTokenAmounts.length); | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokenAmounts.length; i++) { | ||||
|             try | ||||
|                 IBancorNetwork(bancorNetwork) | ||||
|                     .rateByPath | ||||
|                         {gas: BANCOR_CALL_GAS} | ||||
|                         (path, takerTokenAmounts[i]) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         return (bancorNetwork, path, makerTokenAmounts); | ||||
|         (bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts); | ||||
|  | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(bancorNetwork, path), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromBancor | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|  | ||||
|         return (bancorNetwork, path, gasUsed, makerTokenAmounts); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Bancor. Unimplemented | ||||
| @@ -87,6 +111,7 @@ contract BancorSampler is CompilerHack { | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return bancorNetwork the Bancor Network address | ||||
|     /// @return path the selected conversion path from bancor | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromBancor( | ||||
| @@ -97,7 +122,7 @@ contract BancorSampler is CompilerHack { | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts) | ||||
|         returns (address bancorNetwork, address[] memory path, uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,142 +20,99 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/ICurve.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinCurve.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract CurveSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinCurve, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Information for sampling from curve sources. | ||||
|     struct CurveInfo { | ||||
|         address poolAddress; | ||||
|         bytes4 sellQuoteFunctionSelector; | ||||
|         bytes4 buyQuoteFunctionSelector; | ||||
|     } | ||||
|  | ||||
|     /// @dev Base gas limit for Curve calls. Some Curves have multiple tokens | ||||
|     ///      So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens. | ||||
|     uint256 constant private CURVE_CALL_GAS = 2000e3; // Was 600k for Curve but SnowSwap is using 1500k+ | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinCurve(weth) | ||||
|     { } | ||||
|  | ||||
|     function sampleSwapFromCurve( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeCurveInternal( | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Curve. | ||||
|     /// @param curveInfo Curve information specific to this token pair. | ||||
|     /// @param fromTokenIdx Index of the taker token (what to sell). | ||||
|     /// @param toTokenIdx Index of the maker token (what to buy). | ||||
|     /// @param takerToken The taker token to sell. | ||||
|     /// @param makerToken The maker token to buy. | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromCurve( | ||||
|         CurveInfo memory curveInfo, | ||||
|         int128 fromTokenIdx, | ||||
|         int128 toTokenIdx, | ||||
|         CurveBridgeData memory curveInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         curveInfo.sellQuoteFunctionSelector, | ||||
|                         fromTokenIdx, | ||||
|                         toTokenIdx, | ||||
|                         takerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 buyAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 buyAmount = abi.decode(resultData, (uint256)); | ||||
|             } | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(curveInfo), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromCurve | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Curve. | ||||
|     /// @param curveInfo Curve information specific to this token pair. | ||||
|     /// @param fromTokenIdx Index of the taker token (what to sell). | ||||
|     /// @param toTokenIdx Index of the maker token (what to buy). | ||||
|     /// @param takerToken The taker token to sell. | ||||
|     /// @param makerToken The maker token to buy. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromCurve( | ||||
|         CurveInfo memory curveInfo, | ||||
|         int128 fromTokenIdx, | ||||
|         int128 toTokenIdx, | ||||
|         CurveBridgeData memory curveInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) { | ||||
|             // Buys not supported on this curve, so approximate it. | ||||
|             return _sampleApproximateBuys( | ||||
|                 ApproximateBuyQuoteOpts({ | ||||
|                     makerTokenData: abi.encode(toTokenIdx, curveInfo), | ||||
|                     takerTokenData: abi.encode(fromTokenIdx, curveInfo), | ||||
|                     getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve | ||||
|                 }), | ||||
|                 makerTokenAmounts | ||||
|             ); | ||||
|         } | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         curveInfo.buyQuoteFunctionSelector, | ||||
|                         fromTokenIdx, | ||||
|                         toTokenIdx, | ||||
|                         makerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 sellAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 sellAmount = abi.decode(resultData, (uint256)); | ||||
|             } | ||||
|             takerTokenAmounts[i] = sellAmount; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (takerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromCurve( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         (int128 takerTokenIdx, CurveInfo memory curveInfo) = | ||||
|             abi.decode(takerTokenData, (int128, CurveInfo)); | ||||
|         (int128 makerTokenIdx) = | ||||
|             abi.decode(makerTokenData, (int128)); | ||||
|         (bool success, bytes memory resultData) = | ||||
|             address(this).staticcall(abi.encodeWithSelector( | ||||
|                 this.sampleSellsFromCurve.selector, | ||||
|                 curveInfo, | ||||
|                 takerTokenIdx, | ||||
|                 makerTokenIdx, | ||||
|                 _toSingleValueArray(sellAmount) | ||||
|             )); | ||||
|         if (!success) { | ||||
|             return 0; | ||||
|         } | ||||
|         // solhint-disable-next-line indent | ||||
|         return abi.decode(resultData, (uint256[]))[0]; | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(curveInfo), | ||||
|                 buyTokenData: abi.encode( | ||||
|                     CurveBridgeData({ | ||||
|                         curveAddress: curveInfo.curveAddress, | ||||
|                         exchangeFunctionSelector: curveInfo.exchangeFunctionSelector, | ||||
|                         fromCoinIdx: curveInfo.toCoinIdx, | ||||
|                         toCoinIdx: curveInfo.fromCoinIdx | ||||
|                     }) | ||||
|                 ), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromCurve | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										112
									
								
								packages/asset-swapper/contracts/src/CurveV2Sampler.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								packages/asset-swapper/contracts/src/CurveV2Sampler.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| // 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; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinCurveV2.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract CurveV2Sampler is | ||||
|     MixinCurveV2, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     function sampleSwapFromCurveV2( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeCurveV2( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Curve. | ||||
|     /// @param curveInfo Curve information specific to this token pair. | ||||
|     /// @param takerToken The taker token to sell. | ||||
|     /// @param makerToken The maker token to buy. | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromCurveV2( | ||||
|         CurveBridgeDataV2 memory curveInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(curveInfo), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromCurveV2 | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Curve. | ||||
|     /// @param curveInfo Curve information specific to this token pair. | ||||
|     /// @param takerToken The taker token to sell. | ||||
|     /// @param makerToken The maker token to buy. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromCurveV2( | ||||
|         CurveBridgeDataV2 memory curveInfo, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(curveInfo), | ||||
|                 buyTokenData: abi.encode( | ||||
|                     CurveBridgeDataV2({ | ||||
|                         curveAddress: curveInfo.curveAddress, | ||||
|                         exchangeFunctionSelector: curveInfo.exchangeFunctionSelector, | ||||
|                         fromCoinIdx: curveInfo.toCoinIdx, | ||||
|                         toCoinIdx: curveInfo.fromCoinIdx | ||||
|                     }) | ||||
|                 ), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromCurveV2 | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -20,35 +20,40 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinDodo.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| interface IDODOZoo { | ||||
|     function getDODO(address baseToken, address quoteToken) external view returns (address); | ||||
| } | ||||
|  | ||||
| interface IDODOHelper { | ||||
|     function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256); | ||||
| } | ||||
|  | ||||
| interface IDODO { | ||||
|     function querySellBaseToken(uint256 amount) external view returns (uint256); | ||||
|     function _TRADE_ALLOWED_() external view returns (bool); | ||||
| } | ||||
|  | ||||
| contract DODOSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinDodo, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     /// @dev Gas limit for DODO calls. | ||||
|     uint256 constant private DODO_CALL_GAS = 300e3; // 300k | ||||
|     struct DODOSamplerOpts { | ||||
|         address registry; | ||||
|         address helper; | ||||
|     } | ||||
|  | ||||
|     function sampleSwapFromDodo( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeDodo( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from DODO. | ||||
|     /// @param opts DODOSamplerOpts DODO Registry and helper addresses | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
| @@ -56,6 +61,7 @@ contract DODOSampler is | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return sellBase whether the bridge needs to sell the base token | ||||
|     /// @return pool the DODO pool address | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromDODO( | ||||
| @@ -65,13 +71,13 @@ contract DODOSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts) | ||||
|         returns ( | ||||
|             bool sellBase, | ||||
|             address pool, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken); | ||||
|         address baseToken; | ||||
|         // If pool exists we have the correct order of Base/Quote | ||||
| @@ -82,29 +88,21 @@ contract DODOSampler is | ||||
|             pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken); | ||||
|             // No pool either direction | ||||
|             if (address(pool) == address(0)) { | ||||
|                 return (sellBase, pool, makerTokenAmounts); | ||||
|                 return (sellBase, pool, gasUsed, makerTokenAmounts); | ||||
|             } | ||||
|             baseToken = makerToken; | ||||
|             sellBase = false; | ||||
|         } | ||||
|  | ||||
|         // DODO Pool has been disabled | ||||
|         if (!IDODO(pool)._TRADE_ALLOWED_()) { | ||||
|             return (sellBase, pool, makerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 buyAmount = _sampleSellForApproximateBuyFromDODO( | ||||
|                 abi.encode(takerToken, pool, baseToken, opts.helper), // taker token data | ||||
|                 abi.encode(makerToken, pool, baseToken, opts.helper), // maker token data | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(opts.helper, pool, sellBase), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromDodo | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from DODO. | ||||
| @@ -114,6 +112,7 @@ contract DODOSampler is | ||||
|     /// @param makerTokenAmounts Maker token sell amount for each sample. | ||||
|     /// @return sellBase whether the bridge needs to sell the base token | ||||
|     /// @return pool the DODO pool address | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromDODO( | ||||
| @@ -123,13 +122,13 @@ contract DODOSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) | ||||
|         returns ( | ||||
|             bool sellBase, | ||||
|             address pool, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory takerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         // Pool is BASE/QUOTE | ||||
|         // Look up the pool from the taker/maker combination | ||||
|         pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken); | ||||
| @@ -143,69 +142,21 @@ contract DODOSampler is | ||||
|             pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken); | ||||
|             // No pool either direction | ||||
|             if (address(pool) == address(0)) { | ||||
|                 return (sellBase, pool, takerTokenAmounts); | ||||
|                 return (sellBase, pool, gasUsed, takerTokenAmounts); | ||||
|             } | ||||
|             baseToken = makerToken; | ||||
|             sellBase = false; | ||||
|         } | ||||
|  | ||||
|         // DODO Pool has been disabled | ||||
|         if (!IDODO(pool)._TRADE_ALLOWED_()) { | ||||
|             return (sellBase, pool, takerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         takerTokenAmounts = _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, pool, baseToken, opts.helper), | ||||
|                 takerTokenData: abi.encode(takerToken, pool, baseToken, opts.helper), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(opts.helper, pool, sellBase), | ||||
|                 buyTokenData: abi.encode(opts.helper, pool, !sellBase), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromDodo | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromDODO( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory /* makerTokenData */, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         (address takerToken, address pool, address baseToken, address helper) = abi.decode( | ||||
|             takerTokenData, | ||||
|             (address, address, address, address) | ||||
|         ); | ||||
|  | ||||
|         // We will get called to sell both the taker token and also to sell the maker token | ||||
|         if (takerToken == baseToken) { | ||||
|             // If base token then use the original query on the pool | ||||
|             try | ||||
|                 IDODO(pool).querySellBaseToken | ||||
|                     {gas: DODO_CALL_GAS} | ||||
|                     (sellAmount) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 return amount; | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 return 0; | ||||
|             } | ||||
|         } else { | ||||
|             // If quote token then use helper, this is less accurate | ||||
|             try | ||||
|                 IDODOHelper(helper).querySellQuoteToken | ||||
|                     {gas: DODO_CALL_GAS} | ||||
|                     (pool, sellAmount) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 return amount; | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -20,8 +20,8 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| interface IDODOV2Registry { | ||||
|     function getDODOPool(address baseToken, address quoteToken) | ||||
| @@ -30,26 +30,30 @@ interface IDODOV2Registry { | ||||
|         returns (address[] memory machines); | ||||
| } | ||||
|  | ||||
| interface IDODOV2Pool { | ||||
|     function querySellBase(address trader, uint256 payBaseAmount) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 receiveQuoteAmount, uint256 mtFee); | ||||
|  | ||||
|     function querySellQuote(address trader, uint256 payQuoteAmount) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 receiveBaseAmount, uint256 mtFee); | ||||
| } | ||||
|  | ||||
| contract DODOV2Sampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinDodoV2, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     /// @dev Gas limit for DODO V2 calls. | ||||
|     uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k | ||||
|  | ||||
|     function sampleSwapFromDodoV2( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeDodoV2( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from DODO V2. | ||||
|     /// @param registry Address of the registry to look up. | ||||
|     /// @param offset offset index for the pool in the registry. | ||||
| @@ -58,6 +62,7 @@ contract DODOV2Sampler is | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return sellBase whether the bridge needs to sell the base token | ||||
|     /// @return pool the DODO pool address | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromDODOV2( | ||||
| @@ -68,31 +73,27 @@ contract DODOV2Sampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts) | ||||
|         returns ( | ||||
|             bool sellBase, | ||||
|             address pool, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|  | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         (pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken); | ||||
|         if (pool == address(0)) { | ||||
|             return (sellBase, pool, makerTokenAmounts); | ||||
|             return (sellBase, pool, gasUsed, makerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2( | ||||
|                 abi.encode(takerToken, pool, sellBase), // taker token data | ||||
|                 abi.encode(makerToken, pool, sellBase), // maker token data | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(pool, sellBase), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromDodoV2 | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from DODO. | ||||
| @@ -103,6 +104,7 @@ contract DODOV2Sampler is | ||||
|     /// @param makerTokenAmounts Maker token sell amount for each sample. | ||||
|     /// @return sellBase whether the bridge needs to sell the base token | ||||
|     /// @return pool the DODO pool address | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromDODOV2( | ||||
| @@ -113,68 +115,30 @@ contract DODOV2Sampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) | ||||
|         returns ( | ||||
|             bool sellBase, | ||||
|             address pool, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory takerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         (pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken); | ||||
|         if (pool == address(0)) { | ||||
|             return (sellBase, pool, takerTokenAmounts); | ||||
|             return (sellBase, pool, gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         takerTokenAmounts = _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, pool, !sellBase), | ||||
|                 takerTokenData: abi.encode(takerToken, pool, sellBase), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2 | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(pool, sellBase), | ||||
|                 buyTokenData: abi.encode(pool, !sellBase), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromDodoV2 | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromDODOV2( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory /* makerTokenData */, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         (address takerToken, address pool, bool sellBase) = abi.decode( | ||||
|             takerTokenData, | ||||
|             (address, address, bool) | ||||
|         ); | ||||
|  | ||||
|         // We will get called to sell both the taker token and also to sell the maker token | ||||
|         // since we use approximate buy for sell and buy functions | ||||
|         if (sellBase) { | ||||
|             try | ||||
|                 IDODOV2Pool(pool).querySellBase | ||||
|                     { gas: DODO_V2_CALL_GAS } | ||||
|                     (address(0), sellAmount) | ||||
|                 returns (uint256 amount, uint256) | ||||
|             { | ||||
|                 return amount; | ||||
|             } catch { | ||||
|                 return 0; | ||||
|             } | ||||
|         } else { | ||||
|             try | ||||
|                 IDODOV2Pool(pool).querySellQuote | ||||
|                     { gas: DODO_V2_CALL_GAS } | ||||
|                     (address(0), sellAmount) | ||||
|                 returns (uint256 amount, uint256) | ||||
|             { | ||||
|                 return amount; | ||||
|             } catch { | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _getNextDODOV2Pool( | ||||
|         address registry, | ||||
|         uint256 offset, | ||||
|   | ||||
							
								
								
									
										26
									
								
								packages/asset-swapper/contracts/src/DelegateHackedERC20.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/asset-swapper/contracts/src/DelegateHackedERC20.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
| contract DelegateHackedERC20 { | ||||
|  | ||||
|     address private constant HACKED = 0xDEf1000000000000000000000000000000DE7d37; | ||||
|  | ||||
|     receive() external payable {} | ||||
|  | ||||
|     fallback() payable external { | ||||
|         (bool success, bytes memory resultData) = | ||||
|             HACKED.delegatecall(msg.data); | ||||
|         if (!success) { | ||||
|             assembly { revert(add(resultData, 32), mload(resultData)) } | ||||
|         } | ||||
|         assembly { return(add(resultData, 32), mload(resultData)) } | ||||
|     } | ||||
|  | ||||
|     /// @dev Hack to get around schema validation | ||||
|     function _garbage() | ||||
|         external | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -24,6 +24,7 @@ import "./BalancerSampler.sol"; | ||||
| import "./BalancerV2Sampler.sol"; | ||||
| import "./BancorSampler.sol"; | ||||
| import "./CurveSampler.sol"; | ||||
| import "./CurveV2Sampler.sol"; | ||||
| import "./DODOSampler.sol"; | ||||
| import "./DODOV2Sampler.sol"; | ||||
| import "./Eth2DaiSampler.sol"; | ||||
| @@ -32,24 +33,24 @@ import "./KyberDmmSampler.sol"; | ||||
| import "./LidoSampler.sol"; | ||||
| import "./LiquidityProviderSampler.sol"; | ||||
| import "./MakerPSMSampler.sol"; | ||||
| import "./MultiBridgeSampler.sol"; | ||||
| import "./MStableSampler.sol"; | ||||
| import "./MooniswapSampler.sol"; | ||||
| import "./NativeOrderSampler.sol"; | ||||
| import "./ShellSampler.sol"; | ||||
| import "./SmoothySampler.sol"; | ||||
| import "./TwoHopSampler.sol"; | ||||
| import "./UniswapSampler.sol"; | ||||
| import "./UniswapV2Sampler.sol"; | ||||
| import "./UniswapV3Sampler.sol"; | ||||
| import "./UtilitySampler.sol"; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
|  | ||||
| contract ERC20BridgeSampler is | ||||
|     BalancerSampler, | ||||
|     BalancerV2Sampler, | ||||
|     BancorSampler, | ||||
|     CurveSampler, | ||||
|     CurveV2Sampler, | ||||
|     DODOSampler, | ||||
|     DODOV2Sampler, | ||||
|     Eth2DaiSampler, | ||||
| @@ -60,10 +61,8 @@ contract ERC20BridgeSampler is | ||||
|     MakerPSMSampler, | ||||
|     MStableSampler, | ||||
|     MooniswapSampler, | ||||
|     MultiBridgeSampler, | ||||
|     NativeOrderSampler, | ||||
|     ShellSampler, | ||||
|     SmoothySampler, | ||||
|     TwoHopSampler, | ||||
|     UniswapSampler, | ||||
|     UniswapV2Sampler, | ||||
| @@ -76,11 +75,22 @@ contract ERC20BridgeSampler is | ||||
|         bool success; | ||||
|     } | ||||
|  | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         BancorSampler(weth) | ||||
|         CurveSampler(weth) | ||||
|         KyberSampler(weth) | ||||
|         MooniswapSampler(weth) | ||||
|         LidoSampler(weth) | ||||
|         UniswapSampler(weth) | ||||
|     { } | ||||
|  | ||||
|     /// @dev Call multiple public functions on this contract in a single transaction. | ||||
|     /// @param callDatas ABI-encoded call data for each function call. | ||||
|     /// @return callResults ABI-encoded results data for each call. | ||||
|     function batchCall(bytes[] calldata callDatas) | ||||
|         external | ||||
|         payable | ||||
|         returns (CallResults[] memory callResults) | ||||
|     { | ||||
|         callResults = new CallResults[](callDatas.length); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,21 +20,38 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IEth2Dai.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinOasis.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract Eth2DaiSampler is | ||||
|     SamplerUtils | ||||
|     MixinOasis, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Base gas limit for Eth2Dai calls. | ||||
|     uint256 constant private ETH2DAI_CALL_GAS = 1000e3; // 1m | ||||
|  | ||||
|     function sampleSwapFromOasis( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeOasis( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Eth2Dai/Oasis. | ||||
|     /// @param router Address of the Eth2Dai/Oasis contract | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromEth2Dai( | ||||
| @@ -44,29 +61,17 @@ contract Eth2DaiSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IEth2Dai(router).getBuyAmount | ||||
|                     {gas: ETH2DAI_CALL_GAS} | ||||
|                     (makerToken, takerToken, takerTokenAmounts[i]) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromOasis | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Eth2Dai/Oasis. | ||||
| @@ -74,6 +79,7 @@ contract Eth2DaiSampler is | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Maker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromEth2Dai( | ||||
| @@ -83,28 +89,17 @@ contract Eth2DaiSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IEth2Dai(router).getPayAmount | ||||
|                     {gas: ETH2DAI_CALL_GAS} | ||||
|                     (takerToken, makerToken, makerTokenAmounts[i]) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 takerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (takerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(router), | ||||
|                 buyTokenData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromOasis | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								packages/asset-swapper/contracts/src/GasOverhead.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/asset-swapper/contracts/src/GasOverhead.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
| contract GasOverhead { | ||||
|     uint256 private _overhead = 2; | ||||
|     // Overhead incurred from updating the overhead storage slot | ||||
|     uint256 constant SSTORE_OVERHEAD = 20000; | ||||
|  | ||||
|     function addOverhead(uint256 gas, uint256 gasBefore) | ||||
|         external | ||||
|     { | ||||
|         uint256 callOverhead = gasBefore - gasleft(); | ||||
|         // Add additional est overhead of performing this update | ||||
|         _overhead += gas + callOverhead + SSTORE_OVERHEAD; | ||||
|     } | ||||
|  | ||||
|     function clearOverhead() | ||||
|         external | ||||
|     { | ||||
|         _overhead = 2; | ||||
|     } | ||||
|  | ||||
|     function overhead() | ||||
|         external | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _overhead; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										323
									
								
								packages/asset-swapper/contracts/src/HackedERC20.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								packages/asset-swapper/contracts/src/HackedERC20.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; | ||||
| import "./GasOverhead.sol"; | ||||
|  | ||||
| contract HackedERC20 { | ||||
|  | ||||
|     using LibERC20TokenV06 for IERC20TokenV06; | ||||
|  | ||||
|     struct ShadowedAmount { | ||||
|         bool isShadowed; | ||||
|         uint256 lastTrueAmount; | ||||
|         uint256 shadowedAmount; | ||||
|     } | ||||
|  | ||||
|     struct Storage { | ||||
|         mapping(address=>ShadowedAmount) shadowedBalances; | ||||
|         mapping(address=>mapping(address=>ShadowedAmount)) shadowedAllowances; | ||||
|         // When enabled the HackedERC20 token will shadow and track balances | ||||
|         // when disabled (default) it will call the original implementation | ||||
|         bool enabled; | ||||
|     } | ||||
|  | ||||
|     bytes32 private constant STORAGE_SLOT = 0x64fd48372774b9637ace5c8c7a951f04ea13c793935207f2eada5382a0ec82cb; | ||||
|     GasOverhead private constant GAS_OVERHEAD = GasOverhead(0xDeF1000000000000000000000000000000001337); | ||||
|  | ||||
|     // HackedERC20 also has the overhead of being Delegated to from the replaced token | ||||
|     // USDT->DelegateHackedERC20->HackedERC20 | ||||
|     uint256 private constant DELEGATE_CALL_OVERHEAD = 5000; | ||||
|  | ||||
|     receive() external payable {} | ||||
|  | ||||
|     fallback() payable external { | ||||
|         bytes memory r = _forwardCallToImpl(); | ||||
|         assembly { return(add(r, 32), mload(r)) } | ||||
|     } | ||||
|  | ||||
|     function balanceOf(address owner) | ||||
|         external | ||||
|         /* view */ | ||||
|         returns (uint256 balance) | ||||
|     { | ||||
|         if (!_isEnabled()) { | ||||
|             bytes memory r = _forwardCallToImpl(); | ||||
|             assembly { return(add(r, 32), mload(r)) } | ||||
|         } | ||||
|         (ShadowedAmount memory sBal,) = _getSyncedBalance(owner); | ||||
|         return sBal.shadowedAmount; | ||||
|     } | ||||
|  | ||||
|     function allowance(address owner, address spender) | ||||
|         external | ||||
|         /* view */ | ||||
|         returns (uint256 allowance_) | ||||
|     { | ||||
|         if (!_isEnabled()) { | ||||
|             bytes memory r = _forwardCallToImpl(); | ||||
|             assembly { return(add(r, 32), mload(r)) } | ||||
|         } | ||||
|         (ShadowedAmount memory sBal,) = _getSyncedAllowance(owner, spender); | ||||
|         return sBal.shadowedAmount; | ||||
|     } | ||||
|  | ||||
|     function transferFrom(address from, address to, uint256 amount) | ||||
|         public | ||||
|         returns (bool success) | ||||
|     { | ||||
|         if (!_isEnabled()) { | ||||
|             bytes memory r = _forwardCallToImpl(); | ||||
|             assembly { return(add(r, 32), mload(r)) } | ||||
|         } | ||||
|         _updateAllowance(from, amount); | ||||
|         success = _transferFromInternal(from, to, amount); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function transfer(address to, uint256 amount) | ||||
|         external | ||||
|         returns (bool success) | ||||
|     { | ||||
|         if (!_isEnabled()) { | ||||
|             bytes memory r = _forwardCallToImpl(); | ||||
|             assembly { return(add(r, 32), mload(r)) } | ||||
|         } | ||||
|         success = _transferFromInternal(msg.sender, to, amount); | ||||
|     } | ||||
|  | ||||
|     function approve(address spender, uint256 amount) | ||||
|         external | ||||
|         returns (bool) | ||||
|     { | ||||
|         if (!_isEnabled()) { | ||||
|             bytes memory r = _forwardCallToImpl(); | ||||
|             assembly { return(add(r, 32), mload(r)) } | ||||
|         } | ||||
|         ( | ||||
|             ShadowedAmount memory sAllowance, | ||||
|         ) = _getSyncedAllowance(msg.sender, spender); | ||||
|  | ||||
|         sAllowance.shadowedAmount = amount; | ||||
|         _writeSyncedAllowance(msg.sender, spender, sAllowance); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function _setBalance(address owner, uint256 amount) | ||||
|         public | ||||
|     { | ||||
|         (ShadowedAmount memory sBal,) = _getSyncedBalance(owner); | ||||
|         sBal.shadowedAmount = amount; | ||||
|         _writeSyncedBalance(owner, sBal); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function _getSyncedAllowance(address owner, address spender) | ||||
|         private | ||||
|         /* view */ | ||||
|         returns (ShadowedAmount memory sAllowance, uint256 gasOverhead) | ||||
|     { | ||||
|         uint256 trueAmount = abi.decode( | ||||
|             _forwardCallToImpl(abi.encodeWithSelector( | ||||
|                 IERC20TokenV06.allowance.selector, | ||||
|                 owner, | ||||
|                 spender | ||||
|             )), | ||||
|             (uint256) | ||||
|         ); | ||||
|         // We only want to measure the cost of the underlying token storage lookup | ||||
|         // Not including the excess overhead of our shadow lookup | ||||
|         uint256 gasBefore = gasleft(); | ||||
|         sAllowance = _getStorage().shadowedAllowances[owner][spender]; | ||||
|         _syncShadowedAmount(sAllowance, trueAmount); | ||||
|         gasOverhead = gasBefore - gasleft(); | ||||
|     } | ||||
|  | ||||
|     function _getSyncedBalance(address owner) | ||||
|         private | ||||
|         returns (ShadowedAmount memory sBal, uint256 gasOverhead) | ||||
|     { | ||||
|         uint256 trueAmount = abi.decode( | ||||
|             _forwardCallToImpl(abi.encodeWithSelector( | ||||
|                 IERC20TokenV06.balanceOf.selector, | ||||
|                 owner | ||||
|             )), | ||||
|             (uint256) | ||||
|         ); | ||||
|         // We only want to measure the cost of the underlying token storage lookup | ||||
|         // Not including the excess overhead of our shadow lookup | ||||
|         uint256 gasBefore = gasleft(); | ||||
|         sBal = _getStorage().shadowedBalances[owner]; | ||||
|         _syncShadowedAmount(sBal, trueAmount); | ||||
|         gasOverhead = gasBefore - gasleft(); | ||||
|     } | ||||
|  | ||||
|     function _syncShadowedAmount(ShadowedAmount memory sAmount, uint256 trueAmount) | ||||
|         private | ||||
|         pure | ||||
|     { | ||||
|         if (!sAmount.isShadowed) { | ||||
|             sAmount.isShadowed = true; | ||||
|             sAmount.shadowedAmount = trueAmount; | ||||
|         } else { | ||||
|             // Detect balance changes that can occur from outside of ERC20 | ||||
|             // functions. | ||||
|             if (sAmount.lastTrueAmount > trueAmount) { | ||||
|                 sAmount.shadowedAmount = _sub( | ||||
|                     sAmount.lastTrueAmount, | ||||
|                     sAmount.lastTrueAmount - trueAmount, | ||||
|                     'HackedERC20/SHADOW_ADJUSTMENT_UNDERFLOW' | ||||
|                 ); | ||||
|             } else if (sAmount.lastTrueAmount < trueAmount) { | ||||
|                 sAmount.shadowedAmount = _add( | ||||
|                     sAmount.lastTrueAmount, | ||||
|                     trueAmount - sAmount.lastTrueAmount, | ||||
|                     'HackedERC20/SHADOW_ADJUSTMENT_OVERFLOW' | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|         sAmount.lastTrueAmount = trueAmount; | ||||
|     } | ||||
|  | ||||
|     function _writeSyncedBalance(address owner, ShadowedAmount memory sBal) | ||||
|         private | ||||
|     { | ||||
|         _getStorage().shadowedBalances[owner] = sBal; | ||||
|     } | ||||
|  | ||||
|     function _writeSyncedAllowance( | ||||
|         address owner, | ||||
|         address spender, | ||||
|         ShadowedAmount memory sAllowance | ||||
|     ) | ||||
|         private | ||||
|     { | ||||
|         _getStorage().shadowedAllowances[owner][spender] = sAllowance; | ||||
|     } | ||||
|  | ||||
|     function _getStorage() private pure returns (Storage storage st) { | ||||
|         bytes32 slot = STORAGE_SLOT; | ||||
|         assembly { st_slot := slot } | ||||
|     } | ||||
|  | ||||
|     function _getOriginalImplementationAddress() | ||||
|         private | ||||
|         view | ||||
|         returns (address impl) | ||||
|     { | ||||
|         return address(uint160(address(this)) + 1); | ||||
|     } | ||||
|  | ||||
|     function _forwardCallToImpl() | ||||
|         private | ||||
|         returns (bytes memory resultData) | ||||
|     { | ||||
|         bool success; | ||||
|         (success, resultData) = | ||||
|             _getOriginalImplementationAddress().delegatecall(msg.data); | ||||
|         if (!success) { | ||||
|             assembly { revert(add(resultData, 32), mload(resultData)) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _forwardCallToImpl(bytes memory callData) | ||||
|         private | ||||
|         returns (bytes memory resultData) | ||||
|     { | ||||
|         bool success; | ||||
|         (success, resultData) = | ||||
|             _getOriginalImplementationAddress().delegatecall(callData); | ||||
|         if (!success) { | ||||
|             assembly { revert(add(resultData, 32), mload(resultData)) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _transferFromInternal(address from, address to, uint256 amount) | ||||
|         internal | ||||
|         returns (bool) | ||||
|     { | ||||
|         ShadowedAmount memory sFromBal; | ||||
|         ShadowedAmount memory sToBal; | ||||
|         uint256 gasOverhead; | ||||
|         uint256 _gasOverhead; | ||||
|  | ||||
|         (sFromBal, _gasOverhead) = _getSyncedBalance(from); | ||||
|         gasOverhead += _gasOverhead; | ||||
|         sFromBal.shadowedAmount = _sub( | ||||
|             sFromBal.shadowedAmount, | ||||
|             amount, | ||||
|             'HackedERC20/BALANCE_UNDERFLOW' | ||||
|         ); | ||||
|         _writeSyncedBalance(from, sFromBal); | ||||
|  | ||||
|         (sToBal, _gasOverhead) = _getSyncedBalance(to); | ||||
|         gasOverhead += _gasOverhead; | ||||
|         sToBal.shadowedAmount = _add( | ||||
|             sToBal.shadowedAmount, | ||||
|             amount, | ||||
|             'HackedERC20/BALANCE_OVERFLOW' | ||||
|         ); | ||||
|         _writeSyncedBalance(to, sToBal); | ||||
|  | ||||
|         // Update the global gas overhead from a transfer call | ||||
|         try | ||||
|             GAS_OVERHEAD.addOverhead(gasOverhead + DELEGATE_CALL_OVERHEAD, gasleft()) | ||||
|         { } catch { } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function _updateAllowance(address from, uint256 amount) | ||||
|         internal | ||||
|     { | ||||
|         (ShadowedAmount memory sAllowance, uint256 gasOverhead) = _getSyncedAllowance(from, msg.sender); | ||||
|         if (from != msg.sender && sAllowance.shadowedAmount != uint256(-1)) { | ||||
|             sAllowance.shadowedAmount = _sub( | ||||
|                 sAllowance.shadowedAmount, | ||||
|                 amount, | ||||
|                 'HackedERC20/ALLOWANCE_UNDERFLOW' | ||||
|             ); | ||||
|             _writeSyncedAllowance(from, msg.sender, sAllowance); | ||||
|         } | ||||
|         uint256 gasBefore = gasleft(); | ||||
|         // Assume a NON MAX_UINT results in allowance update SSTORE | ||||
|         _writeSyncedAllowance(from, msg.sender, sAllowance); | ||||
|         gasOverhead += gasBefore - gasleft(); | ||||
|         // Update the global gas overhead from a allowance check | ||||
|         try | ||||
|             GAS_OVERHEAD.addOverhead(gasOverhead + DELEGATE_CALL_OVERHEAD, gasleft()) | ||||
|         { } catch { } | ||||
|     } | ||||
|  | ||||
|     function _isEnabled() | ||||
|         internal | ||||
|         returns (bool) | ||||
|     { | ||||
|         return _getStorage().enabled; | ||||
|     } | ||||
|  | ||||
|     function _setEnabled(bool enabled) | ||||
|         public | ||||
|     { | ||||
|         _getStorage().enabled = enabled; | ||||
|     } | ||||
|  | ||||
|     function _add(uint256 a, uint256 b, string memory errMsg) | ||||
|         private | ||||
|         pure | ||||
|         returns (uint256 c) | ||||
|     { | ||||
|         c = a + b; | ||||
|         require(c >= a, errMsg); | ||||
|     } | ||||
|  | ||||
|     function _sub(uint256 a, uint256 b, string memory errMsg) | ||||
|         private | ||||
|         pure | ||||
|         returns (uint256 c) | ||||
|     { | ||||
|         c = a - b; | ||||
|         require(c <= a, errMsg); | ||||
|     } | ||||
| } | ||||
| @@ -20,6 +20,8 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinKyberDmm.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
| interface IKyberDmmFactory { | ||||
|  | ||||
|     function getPoolAtIndex(address token0, address token1, uint256 index) | ||||
| @@ -28,33 +30,36 @@ interface IKyberDmmFactory { | ||||
|         returns (address); | ||||
| } | ||||
|  | ||||
| interface IKyberDmmRouter { | ||||
|  | ||||
|     function factory() external view returns (address); | ||||
|  | ||||
|     function getAmountsOut(uint256 amountIn, address[] calldata pools, address[] calldata path) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256[] memory amounts); | ||||
|  | ||||
|     function getAmountsIn(uint256 amountOut, address[] calldata pools, address[] calldata path) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256[] memory amounts); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| contract KyberDmmSampler | ||||
| contract KyberDmmSampler is | ||||
|     MixinKyberDmm, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Gas limit for KyberDmm calls. | ||||
|     uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k | ||||
|  | ||||
|     function sampleSwapFromKyberDmm( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeKyberDmm( | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from KyberDmm. | ||||
|     /// @param router Router to look up tokens and amounts | ||||
|     /// @param path Token route. Should be takerToken -> makerToken | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return pools The pool addresses involved in the multi path trade | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromKyberDmm( | ||||
| @@ -63,32 +68,26 @@ contract KyberDmmSampler | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (address[] memory pools, uint256[] memory makerTokenAmounts) | ||||
|         returns ( | ||||
|             address[] memory pools, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         pools = _getKyberDmmPools(router, path); | ||||
|         if (pools.length == 0) { | ||||
|             return (pools, makerTokenAmounts); | ||||
|         } | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IKyberDmmRouter(router).getAmountsOut | ||||
|                     {gas: KYBER_DMM_CALL_GAS} | ||||
|                     (takerTokenAmounts[i], pools, path) | ||||
|                 returns (uint256[] memory amounts) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amounts[path.length - 1]; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|             return (pools, gasUsed, makerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: path[0], | ||||
|                 buyToken: path[path.length - 1], | ||||
|                 bridgeData: abi.encode(router, pools, path), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromKyberDmm | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from KyberDmm. | ||||
| @@ -96,6 +95,7 @@ contract KyberDmmSampler | ||||
|     /// @param path Token route. Should be takerToken -> makerToken. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return pools The pool addresses involved in the multi path trade | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromKyberDmm( | ||||
| @@ -104,32 +104,32 @@ contract KyberDmmSampler | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (address[] memory pools, uint256[] memory takerTokenAmounts) | ||||
|         returns ( | ||||
|             address[] memory pools, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory takerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         pools = _getKyberDmmPools(router, path); | ||||
|         if (pools.length == 0) { | ||||
|             return (pools, takerTokenAmounts); | ||||
|             return (pools, gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IKyberDmmRouter(router).getAmountsIn | ||||
|                     {gas: KYBER_DMM_CALL_GAS} | ||||
|                     (makerTokenAmounts[i], pools, path) | ||||
|                 returns (uint256[] memory amounts) | ||||
|             { | ||||
|                 takerTokenAmounts[i] = amounts[0]; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (takerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|         address[] memory reversedPath = new address[](path.length); | ||||
|         for (uint256 i = 0; i < path.length; ++i) { | ||||
|             reversedPath[i] = path[path.length - i - 1]; | ||||
|         } | ||||
|         address[] memory reversedPools = _getKyberDmmPools(router, reversedPath); | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: path[0], | ||||
|                 buyToken: path[path.length - 1], | ||||
|                 sellTokenData: abi.encode(router, pools, path), | ||||
|                 buyTokenData: abi.encode(router, reversedPools, reversedPath), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromKyberDmm | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _getKyberDmmPools( | ||||
|   | ||||
| @@ -21,18 +21,22 @@ pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IKyberNetwork.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
|  | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinKyber.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract KyberSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinKyber, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     /// @dev Gas limit for Kyber calls. | ||||
|     uint256 constant private KYBER_CALL_GAS = 500e3; // 500k | ||||
|     /// @dev Kyber ETH pseudo-address. | ||||
|     address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinKyber(weth) | ||||
|     { } | ||||
|  | ||||
|     struct KyberSamplerOpts { | ||||
|         uint256 reserveOffset; | ||||
| @@ -42,6 +46,26 @@ contract KyberSampler is | ||||
|         bytes hint; | ||||
|     } | ||||
|  | ||||
|     function sampleSwapFromKyber( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeKyberInternal( | ||||
|             // these are Immutable in MixinKyber, since they are only set in constructor they must be passed in | ||||
|             IERC20TokenV06(KYBER_ETH_ADDRESS), | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Kyber. | ||||
|     /// @param opts KyberSamplerOpts The nth reserve | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
| @@ -49,6 +73,7 @@ contract KyberSampler is | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return reserveId The id of the reserve found at reserveOffset | ||||
|     /// @return hint The hint for the selected reserve | ||||
|     /// @return gasUsed Gas consumed per sample. | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token amount. | ||||
|     function sampleSellsFromKyberNetwork( | ||||
|         KyberSamplerOpts memory opts, | ||||
| @@ -57,32 +82,29 @@ contract KyberSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bytes32 reserveId, bytes memory hint, uint256[] memory makerTokenAmounts) | ||||
|         returns ( | ||||
|             bytes32 reserveId, | ||||
|             bytes memory hint, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         reserveId = _getNextReserveId(opts, takerToken, makerToken); | ||||
|         if (reserveId == 0x0) { | ||||
|             return (reserveId, hint, makerTokenAmounts); | ||||
|             return (reserveId, hint, gasUsed, makerTokenAmounts); | ||||
|         } | ||||
|         opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken); | ||||
|         hint = opts.hint; | ||||
|  | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 value = this.sampleSellFromKyberNetwork( | ||||
|                 opts, | ||||
|                 takerToken, | ||||
|                 makerToken, | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             makerTokenAmounts[i] = value; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(opts.networkProxy, hint), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromKyber | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Kyber. | ||||
| @@ -92,6 +114,7 @@ contract KyberSampler is | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return reserveId The id of the reserve found at reserveOffset | ||||
|     /// @return hint The hint for the selected reserve | ||||
|     /// @return gasUsed Gas consumed for each sample. | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token amount. | ||||
|     function sampleBuysFromKyberNetwork( | ||||
|         KyberSamplerOpts memory opts, | ||||
| @@ -100,27 +123,30 @@ contract KyberSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (bytes32 reserveId, bytes memory hint, uint256[] memory takerTokenAmounts) | ||||
|         returns ( | ||||
|             bytes32 reserveId, | ||||
|             bytes memory hint, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory takerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|  | ||||
|         reserveId = _getNextReserveId(opts, takerToken, makerToken); | ||||
|         if (reserveId == 0x0) { | ||||
|             return (reserveId, hint, takerTokenAmounts); | ||||
|             return (reserveId, hint, gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|         opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken); | ||||
|         hint = opts.hint; | ||||
|  | ||||
|         takerTokenAmounts = _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, opts), | ||||
|                 takerTokenData: abi.encode(takerToken, opts), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(opts.networkProxy, hint), | ||||
|                 buyTokenData: abi.encode(opts.networkProxy, hint), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromKyber | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|         return (reserveId, hint, takerTokenAmounts); | ||||
|     } | ||||
|  | ||||
|     function encodeKyberHint( | ||||
| @@ -201,73 +227,6 @@ contract KyberSampler is | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromKyber( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         (address makerToken, KyberSamplerOpts memory opts) = | ||||
|             abi.decode(makerTokenData, (address, KyberSamplerOpts)); | ||||
|         (address takerToken, ) = | ||||
|             abi.decode(takerTokenData, (address, KyberSamplerOpts)); | ||||
|         try | ||||
|             this.sampleSellFromKyberNetwork | ||||
|                 (opts, takerToken, makerToken, sellAmount) | ||||
|             returns (uint256 amount) | ||||
|         { | ||||
|             return amount; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function sampleSellFromKyberNetwork( | ||||
|         KyberSamplerOpts memory opts, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256 makerTokenAmount) | ||||
|     { | ||||
|         // If there is no hint do not continue | ||||
|         if (opts.hint.length == 0) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         try | ||||
|             IKyberNetworkProxy(opts.networkProxy).getExpectedRateAfterFee | ||||
|                 {gas: KYBER_CALL_GAS} | ||||
|                 ( | ||||
|                     takerToken == opts.weth ? KYBER_ETH_ADDRESS : takerToken, | ||||
|                     makerToken == opts.weth ? KYBER_ETH_ADDRESS : makerToken, | ||||
|                     takerTokenAmount, | ||||
|                     0, // fee | ||||
|                     opts.hint | ||||
|                 ) | ||||
|             returns (uint256 rate) | ||||
|         { | ||||
|             uint256 makerTokenDecimals = _getTokenDecimals(makerToken); | ||||
|             uint256 takerTokenDecimals = _getTokenDecimals(takerToken); | ||||
|             makerTokenAmount = | ||||
|                 rate * | ||||
|                 takerTokenAmount * | ||||
|                 10 ** makerTokenDecimals / | ||||
|                 10 ** takerTokenDecimals / | ||||
|                 10 ** 18; | ||||
|             return makerTokenAmount; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _getNextReserveId( | ||||
|         KyberSamplerOpts memory opts, | ||||
|         address takerToken, | ||||
|   | ||||
| @@ -20,72 +20,84 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract LidoSampler is SamplerUtils { | ||||
|     struct LidoInfo { | ||||
|         address stEthToken; | ||||
|         address wethToken; | ||||
| contract LidoSampler is | ||||
|     MixinLido, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinLido(weth) | ||||
|     { } | ||||
|  | ||||
|     function sampleSwapFromLido( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeLidoInternal( | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Lido | ||||
|     /// @param lidoInfo Info regarding a specific Lido deployment | ||||
|     /// @param lido Address of the Lido contract | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed for each sample amount | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromLido( | ||||
|         LidoInfo memory lidoInfo, | ||||
|         address lido, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (uint256[] memory) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|  | ||||
|         if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) { | ||||
|             // Return 0 values if not selling WETH for stETH | ||||
|             uint256 numSamples = takerTokenAmounts.length; | ||||
|             uint256[] memory makerTokenAmounts = new uint256[](numSamples); | ||||
|             return makerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         // Minting stETH is always 1:1 therefore we can just return the same amounts back | ||||
|         return takerTokenAmounts; | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(lido), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromLido | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Lido. | ||||
|     /// @param lidoInfo Info regarding a specific Lido deployment | ||||
|     /// @param lido Address of the Lido contract | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed for each sample amount | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromLido( | ||||
|         LidoInfo memory lidoInfo, | ||||
|         address lido, | ||||
|         address takerToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         pure | ||||
|         returns (uint256[] memory) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|  | ||||
|         if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) { | ||||
|             // Return 0 values if not buying stETH for WETH | ||||
|             uint256 numSamples = makerTokenAmounts.length; | ||||
|             uint256[] memory takerTokenAmounts = new uint256[](numSamples); | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         // Minting stETH is always 1:1 therefore we can just return the same amounts back | ||||
|         return makerTokenAmounts; | ||||
|         // 1:1 rate so we can perform an WETH sell | ||||
|         return this.sampleSellsFromLido(lido, takerToken, makerToken, makerTokenAmounts); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,24 +20,38 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinZeroExBridge.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract LiquidityProviderSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinZeroExBridge, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Default gas limit for liquidity provider calls. | ||||
|     uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k | ||||
|  | ||||
|     function sampleSwapFromLiquidityProvider( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeZeroExBridge( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. | ||||
|     /// @param providerAddress Address of the liquidity provider. | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromLiquidityProvider( | ||||
| @@ -47,34 +61,18 @@ contract LiquidityProviderSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         // Initialize array of maker token amounts. | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 ILiquidityProvider(providerAddress).getSellQuote | ||||
|                     {gas: DEFAULT_CALL_GAS} | ||||
|                     ( | ||||
|                         IERC20TokenV06(takerToken), | ||||
|                         IERC20TokenV06(makerToken), | ||||
|                         takerTokenAmounts[i] | ||||
|                     ) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         bytes memory lpData; | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(providerAddress, lpData), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromLiquidityProvider | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. | ||||
| @@ -82,6 +80,7 @@ contract LiquidityProviderSampler is | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromLiquidityProvider( | ||||
| @@ -91,42 +90,18 @@ contract LiquidityProviderSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         takerTokenAmounts = _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, providerAddress), | ||||
|                 takerTokenData: abi.encode(takerToken, providerAddress), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider | ||||
|         bytes memory lpData; | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(providerAddress, lpData), | ||||
|                 buyTokenData: abi.encode(providerAddress, lpData), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromLiquidityProvider | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromLiquidityProvider( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         (address takerToken, address providerAddress) = | ||||
|             abi.decode(takerTokenData, (address, address)); | ||||
|         (address makerToken) = | ||||
|             abi.decode(makerTokenData, (address)); | ||||
|         try | ||||
|             this.sampleSellsFromLiquidityProvider | ||||
|                 {gas: DEFAULT_CALL_GAS} | ||||
|                 (providerAddress, takerToken, makerToken, _toSingleValueArray(sellAmount)) | ||||
|             returns (uint256[] memory amounts) | ||||
|         { | ||||
|             return amounts[0]; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,23 +20,38 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IMStable.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMStable.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract MStableSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinMStable, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Default gas limit for mStable calls. | ||||
|     uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k | ||||
|  | ||||
|     function sampleSwapFromMStable( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeMStable( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from the mStable contract | ||||
|     /// @param router Address of the mStable contract | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromMStable( | ||||
| @@ -46,31 +61,17 @@ contract MStableSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         // Initialize array of maker token amounts. | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IMStable(router).getSwapOutput | ||||
|                     {gas: DEFAULT_CALL_GAS} | ||||
|                     (takerToken, makerToken, takerTokenAmounts[i]) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMStable | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from MStable contract | ||||
| @@ -78,6 +79,7 @@ contract MStableSampler is | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromMStable( | ||||
| @@ -87,41 +89,17 @@ contract MStableSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         return _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, router), | ||||
|                 takerTokenData: abi.encode(takerToken, router), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(router), | ||||
|                 buyTokenData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMStable | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromMStable( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         (address takerToken, address router) = | ||||
|             abi.decode(takerTokenData, (address, address)); | ||||
|         (address makerToken) = | ||||
|             abi.decode(makerTokenData, (address)); | ||||
|         try | ||||
|             this.sampleSellsFromMStable | ||||
|                 (router, takerToken, makerToken, _toSingleValueArray(sellAmount)) | ||||
|             returns (uint256[] memory amounts) | ||||
|         { | ||||
|             return amounts[0]; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,69 +20,13 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; | ||||
|  | ||||
| interface IPSM { | ||||
|     // @dev Get the fee for selling USDC to DAI in PSM | ||||
|     // @return tin toll in [wad] | ||||
|     function tin() external view returns (uint256); | ||||
|     // @dev Get the fee for selling DAI to USDC in PSM | ||||
|     // @return tout toll out [wad] | ||||
|     function tout() external view returns (uint256); | ||||
|  | ||||
|     // @dev Get the address of the PSM state Vat | ||||
|     // @return address of the Vat | ||||
|     function vat() external view returns (address); | ||||
|  | ||||
|     // @dev Get the address of the underlying vault powering PSM | ||||
|     // @return address of gemJoin contract | ||||
|     function gemJoin() external view returns (address); | ||||
|  | ||||
|     // @dev Get the address of DAI | ||||
|     // @return address of DAI contract | ||||
|     function dai() external view returns (address); | ||||
|  | ||||
|     // @dev Sell USDC for DAI | ||||
|     // @param usr The address of the account trading USDC for DAI. | ||||
|     // @param gemAmt The amount of USDC to sell in USDC base units | ||||
|     function sellGem( | ||||
|         address usr, | ||||
|         uint256 gemAmt | ||||
|     ) external; | ||||
|     // @dev Buy USDC for DAI | ||||
|     // @param usr The address of the account trading DAI for USDC | ||||
|     // @param gemAmt The amount of USDC to buy in USDC base units | ||||
|     function buyGem( | ||||
|         address usr, | ||||
|         uint256 gemAmt | ||||
|     ) external; | ||||
| } | ||||
|  | ||||
| interface IVAT { | ||||
|     // @dev Get a collateral type by identifier | ||||
|     // @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A") | ||||
|     // @return ilk | ||||
|     // @return ilk.Art Total Normalised Debt in wad | ||||
|     // @return ilk.rate Accumulated Rates in ray | ||||
|     // @return ilk.spot Price with Safety Margin in ray | ||||
|     // @return ilk.line Debt Ceiling in rad | ||||
|     // @return ilk.dust Urn Debt Floor in rad | ||||
|     function ilks( | ||||
|         bytes32 ilkIdentifier | ||||
|     ) external view returns ( | ||||
|         uint256 Art, | ||||
|         uint256 rate, | ||||
|         uint256 spot, | ||||
|         uint256 line, | ||||
|         uint256 dust | ||||
|     ); | ||||
| } | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMakerPSM.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract MakerPSMSampler is | ||||
|     SamplerUtils | ||||
|     MixinMakerPSM, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     using LibSafeMathV06 for uint256; | ||||
|  | ||||
|     /// @dev Information about which PSM module to use | ||||
|     struct MakerPsmInfo { | ||||
| @@ -91,18 +35,22 @@ contract MakerPSMSampler is | ||||
|         address gemTokenAddress; | ||||
|     } | ||||
|  | ||||
|     /// @dev Gas limit for MakerPsm calls. | ||||
|     uint256 constant private MAKER_PSM_CALL_GAS = 300e3; // 300k | ||||
|  | ||||
|  | ||||
|     // Maker units | ||||
|     // wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances) | ||||
|     uint256 constant private WAD = 10 ** 18; | ||||
|     // ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios) | ||||
|     uint256 constant private RAY = 10 ** 27; | ||||
|     // rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray) | ||||
|     uint256 constant private RAD = 10 ** 45; | ||||
|     // See https://github.com/makerdao/dss/blob/master/DEVELOPING.m | ||||
|     function sampleSwapFromMakerPsm( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeMakerPsm( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Maker PSM | ||||
|     function sampleSellsFromMakerPsm( | ||||
| @@ -112,29 +60,22 @@ contract MakerPSMSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         IPSM psm = IPSM(psmInfo.psmAddress); | ||||
|         IVAT vat = IVAT(psm.vat()); | ||||
|  | ||||
|  | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         if (makerToken != psm.dai() && takerToken != psm.dai()) { | ||||
|             return makerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat); | ||||
|  | ||||
|             if (buyAmount == 0) { | ||||
|                 break; | ||||
|             } | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode( | ||||
|                     MakerPsmBridgeData({ | ||||
|                         psmAddress: psmInfo.psmAddress, | ||||
|                         gemTokenAddres: psmInfo.gemTokenAddress | ||||
|                     }) | ||||
|                 ), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMakerPsm | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function sampleBuysFromMakerPsm( | ||||
| @@ -144,124 +85,21 @@ contract MakerPSMSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         IPSM psm = IPSM(psmInfo.psmAddress); | ||||
|         IVAT vat = IVAT(psm.vat()); | ||||
|  | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         if (makerToken != psm.dai() && takerToken != psm.dai()) { | ||||
|             return takerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat); | ||||
|  | ||||
|             if (sellAmount == 0) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             takerTokenAmounts[i] = sellAmount; | ||||
|         } | ||||
|  | ||||
|         MakerPsmBridgeData memory data = MakerPsmBridgeData({ | ||||
|             psmAddress: psmInfo.psmAddress, | ||||
|             gemTokenAddres: psmInfo.gemTokenAddress | ||||
|         }); | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(data), | ||||
|                 buyTokenData: abi.encode(data), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMakerPsm | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _samplePSMSell(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 takerTokenAmount, IPSM psm, IVAT vat) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         (uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier); | ||||
|         uint256 gemTokenBaseUnit = uint256(1e6); | ||||
|  | ||||
|         if (takerToken == psmInfo.gemTokenAddress) { | ||||
|             // Simulate sellGem | ||||
|             // Selling USDC to the PSM, increasing the total debt | ||||
|             // Convert USDC 6 decimals to 18 decimals [wad] | ||||
|             uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12); | ||||
|  | ||||
|             uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY); | ||||
|  | ||||
|             // PSM is too full to fit | ||||
|             if (newTotalDebtInRad >= debtCeilingInRad) { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD); | ||||
|             uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad); | ||||
|  | ||||
|             return makerTokenAmountInWad; | ||||
|         } else if (makerToken == psmInfo.gemTokenAddress) { | ||||
|             // Simulate buyGem | ||||
|             // Buying USDC from the PSM, decreasing the total debt | ||||
|             // Selling DAI for USDC, already in 18 decimals [wad] | ||||
|             uint256 takerTokenAmountInWad = takerTokenAmount; | ||||
|             if (takerTokenAmountInWad > totalDebtInWad) { | ||||
|                 return 0; | ||||
|             } | ||||
|             uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY); | ||||
|  | ||||
|             // PSM is empty, not enough USDC to buy from it | ||||
|             if (newTotalDebtInRad <= debtFloorInRad) { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout; | ||||
|             uint256 makerTokenAmountInGemTokenBaseUnits =  takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(feeDivisorInWad); | ||||
|  | ||||
|             return makerTokenAmountInGemTokenBaseUnits; | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     function _samplePSMBuy(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 makerTokenAmount, IPSM psm, IVAT vat) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         (uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier); | ||||
|  | ||||
|         if (takerToken == psmInfo.gemTokenAddress) { | ||||
|             // Simulate sellGem | ||||
|             // Selling USDC to the PSM, increasing the total debt | ||||
|             uint256 makerTokenAmountInWad = makerTokenAmount; | ||||
|             uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin; | ||||
|             uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad); | ||||
|             uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY); | ||||
|  | ||||
|             // PSM is too full to fit | ||||
|             if (newTotalDebtInRad >= debtCeilingInRad) { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals | ||||
|  | ||||
|             return takerTokenAmountInGemInGemBaseUnits; | ||||
|         } else if (makerToken == psmInfo.gemTokenAddress) { | ||||
|             // Simulate buyGem | ||||
|             // Buying USDC from the PSM, decreasing the total debt | ||||
|             uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12); | ||||
|             uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout; | ||||
|             uint256 takerTokenAmountInWad =  makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD); | ||||
|             if (takerTokenAmountInWad > totalDebtInWad) { | ||||
|                 return 0; | ||||
|             } | ||||
|             uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY); | ||||
|  | ||||
|             // PSM is empty, not enough USDC to buy | ||||
|             if (newTotalDebtInRad <= debtFloorInRad) { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|  | ||||
|             return takerTokenAmountInWad; | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,17 +20,40 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IMooniswap.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinMooniswap.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| interface IMooniswapRegistry { | ||||
|     function pools(address token1, address token2) external view returns(address); | ||||
| } | ||||
|  | ||||
| contract MooniswapSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinMooniswap, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Gas limit for Mooniswap calls. | ||||
|     uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k | ||||
|  | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinMooniswap(weth) | ||||
|     { } | ||||
|  | ||||
|     function sampleSwapFromMooniswap( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeMooniswapInternal( | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Mooniswap. | ||||
|     /// @param registry Address of the Mooniswap Registry. | ||||
| @@ -38,6 +61,7 @@ contract MooniswapSampler is | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return pool The contract address for the pool | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromMooniswap( | ||||
| @@ -47,69 +71,22 @@ contract MooniswapSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (IMooniswap pool, uint256[] memory makerTokenAmounts) | ||||
|         returns (address pool, uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             uint256 buyAmount = sampleSingleSellFromMooniswapPool( | ||||
|                 registry, | ||||
|                 takerToken, | ||||
|                 makerToken, | ||||
|                 takerTokenAmounts[i] | ||||
|             ); | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         pool = IMooniswap( | ||||
|             IMooniswapRegistry(registry).pools(takerToken, makerToken) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function sampleSingleSellFromMooniswapPool( | ||||
|         address registry, | ||||
|         address mooniswapTakerToken, | ||||
|         address mooniswapMakerToken, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256) | ||||
|     { | ||||
|         // Find the pool for the pair. | ||||
|         IMooniswap pool = IMooniswap( | ||||
|             IMooniswapRegistry(registry).pools(mooniswapTakerToken, mooniswapMakerToken) | ||||
|         ); | ||||
|         // If there is no pool then return early | ||||
|         pool = _getMooniswapPool(registry, takerToken, makerToken); | ||||
|         if (address(pool) == address(0)) { | ||||
|             return 0; | ||||
|         } | ||||
|         uint256 poolBalance = mooniswapTakerToken == address(0) | ||||
|             ? address(pool).balance | ||||
|             : IERC20TokenV06(mooniswapTakerToken).balanceOf(address(pool)); | ||||
|         // If the pool balance is smaller than the sell amount | ||||
|         // don't sample to avoid multiplication overflow in buys | ||||
|         if (poolBalance < takerTokenAmount) { | ||||
|             return 0; | ||||
|         } | ||||
|         try | ||||
|             pool.getReturn | ||||
|                 {gas: MOONISWAP_CALL_GAS} | ||||
|                 (mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount) | ||||
|             returns (uint256 amount) | ||||
|         { | ||||
|             return amount; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|             return (pool, gasUsed, makerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(pool), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMooniswap | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Mooniswap. | ||||
| @@ -118,6 +95,7 @@ contract MooniswapSampler is | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token sell amount for each sample. | ||||
|     /// @return pool The contract address for the pool | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromMooniswap( | ||||
| @@ -127,43 +105,42 @@ contract MooniswapSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (IMooniswap pool, uint256[] memory takerTokenAmounts) | ||||
|         returns (address pool, uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         pool = _getMooniswapPool(registry, takerToken, makerToken); | ||||
|         if (address(pool) == address(0)) { | ||||
|             return (pool, gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         takerTokenAmounts = _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(registry, makerToken), | ||||
|                 takerTokenData: abi.encode(registry, takerToken), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(pool), | ||||
|                 buyTokenData: abi.encode(pool), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromMooniswap | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|  | ||||
|         pool = IMooniswap( | ||||
|             IMooniswapRegistry(registry).pools(takerToken, makerToken) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromMooniswap( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     function _getMooniswapPool( | ||||
|         address registry, | ||||
|         address takerToken, | ||||
|         address makerToken | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|         internal | ||||
|         returns (address pool) | ||||
|     { | ||||
|         (address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address)); | ||||
|         (address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address)); | ||||
|         return sampleSingleSellFromMooniswapPool( | ||||
|             registry, | ||||
|             mooniswapTakerToken, | ||||
|             mooniswapMakerToken, | ||||
|             sellAmount | ||||
|         ); | ||||
|         // WETH is actually ETH in these pools and represented as address(0) | ||||
|         address _takerToken = takerToken == address(_getNativeWrappedToken()) ? address(0) : takerToken; | ||||
|         address _makerToken = makerToken == address(_getNativeWrappedToken()) ? address(0) : makerToken; | ||||
|  | ||||
|         try | ||||
|             IMooniswapRegistry(registry).pools{gas: 300e3}(_takerToken, _makerToken) | ||||
|             returns (address _pool) | ||||
|         { | ||||
|             pool = _pool; | ||||
|         } catch { } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,82 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IMultiBridge.sol"; | ||||
|  | ||||
|  | ||||
| contract MultiBridgeSampler { | ||||
|  | ||||
|     /// @dev Default gas limit for multibridge calls. | ||||
|     uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k | ||||
|  | ||||
|     /// @dev Sample sell quotes from MultiBridge. | ||||
|     /// @param multibridge Address of the MultiBridge contract. | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param intermediateToken The address of the intermediate token to | ||||
|     ///        use in an indirect route. | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromMultiBridge( | ||||
|         address multibridge, | ||||
|         address takerToken, | ||||
|         address intermediateToken, | ||||
|         address makerToken, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         // Initialize array of maker token amounts. | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         // If no address provided, return all zeros. | ||||
|         if (multibridge == address(0)) { | ||||
|             return makerTokenAmounts; | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 multibridge.staticcall.gas(DEFAULT_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         IMultiBridge(0).getSellQuote.selector, | ||||
|                         takerToken, | ||||
|                         intermediateToken, | ||||
|                         makerToken, | ||||
|                         takerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 buyAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 buyAmount = abi.decode(resultData, (uint256)); | ||||
|             } | ||||
|             // Exit early if the amount is too high for the source to serve | ||||
|             if (buyAmount == 0) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             makerTokenAmounts[i] = buyAmount; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; | ||||
|  | ||||
|  | ||||
| contract SamplerUtils { | ||||
|  | ||||
|     /// @dev Overridable way to get token decimals. | ||||
|     /// @param tokenAddress Address of the token. | ||||
|     /// @return decimals The decimal places for the token. | ||||
|     function _getTokenDecimals(address tokenAddress) | ||||
|         virtual | ||||
|         internal | ||||
|         view | ||||
|         returns (uint8 decimals) | ||||
|     { | ||||
|         return LibERC20TokenV06.compatDecimals(IERC20TokenV06(tokenAddress)); | ||||
|     } | ||||
|  | ||||
|     function _toSingleValueArray(uint256 v) | ||||
|         internal | ||||
|         pure | ||||
|         returns (uint256[] memory arr) | ||||
|     { | ||||
|         arr = new uint256[](1); | ||||
|         arr[0] = v; | ||||
|     } | ||||
|  | ||||
|     /// @dev Assert that the tokens in a trade pair are valid. | ||||
|     /// @param makerToken Address of the maker token. | ||||
|     /// @param takerToken Address of the taker token. | ||||
|     function _assertValidPair(address makerToken, address takerToken) | ||||
|         internal | ||||
|         pure | ||||
|     { | ||||
|         require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR"); | ||||
|     } | ||||
| } | ||||
| @@ -20,28 +20,41 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./interfaces/IShell.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
|  | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinShell.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract ShellSampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
|     MixinShell, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|  | ||||
|     struct ShellInfo { | ||||
|         address poolAddress; | ||||
|     } | ||||
|  | ||||
|     /// @dev Default gas limit for Shell calls. | ||||
|     uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k | ||||
|     function sampleSwapFromShell( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeShell( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from the Shell pool contract | ||||
|     /// @param pool Address of the Shell pool contract | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromShell( | ||||
| @@ -51,26 +64,17 @@ contract ShellSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         // Initialize array of maker token amounts. | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IShell(pool).viewOriginSwap | ||||
|                     {gas: DEFAULT_CALL_GAS} | ||||
|                     (takerToken, makerToken, takerTokenAmounts[i]) | ||||
|                 returns (uint256 amount) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amount; | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(pool), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromShell | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Shell pool contract | ||||
| @@ -78,6 +82,7 @@ contract ShellSampler is | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromShell( | ||||
| @@ -87,40 +92,17 @@ contract ShellSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         return _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(makerToken, pool), | ||||
|                 takerTokenData: abi.encode(takerToken, pool), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromShell | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(pool), | ||||
|                 buyTokenData: abi.encode(pool), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromShell | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromShell( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         (address takerToken, address pool) = abi.decode(takerTokenData, (address, address)); | ||||
|         (address makerToken) = abi.decode(makerTokenData, (address)); | ||||
|  | ||||
|         try | ||||
|             this.sampleSellsFromShell | ||||
|                 (pool, takerToken, makerToken, _toSingleValueArray(sellAmount)) | ||||
|             returns (uint256[] memory amounts) | ||||
|         { | ||||
|             return amounts[0]; | ||||
|         } catch (bytes memory) { | ||||
|             // Swallow failures, leaving all results as zero. | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,156 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| // import "./interfaces/ISmoothy.sol"; | ||||
| import "./ApproximateBuys.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
| import "./interfaces/ISmoothy.sol"; | ||||
|  | ||||
| contract SmoothySampler is | ||||
|     SamplerUtils, | ||||
|     ApproximateBuys | ||||
| { | ||||
|     /// @dev Information for sampling from smoothy sources. | ||||
|     struct SmoothyInfo { | ||||
|         address poolAddress; | ||||
|         bytes4 sellQuoteFunctionSelector; | ||||
|         bytes4 buyQuoteFunctionSelector; | ||||
|     } | ||||
|  | ||||
|     /// @dev Base gas limit for Smoothy calls. | ||||
|     uint256 constant private SMOOTHY_CALL_GAS = 600e3; | ||||
|  | ||||
|     /// @dev Sample sell quotes from Smoothy. | ||||
|     /// @param smoothyInfo Smoothy information specific to this token pair. | ||||
|     /// @param fromTokenIdx Index of the taker token (what to sell). | ||||
|     /// @param toTokenIdx Index of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromSmoothy( | ||||
|         SmoothyInfo memory smoothyInfo, | ||||
|         int128 fromTokenIdx, | ||||
|         int128 toTokenIdx, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         // Basically a Curve fork | ||||
|  | ||||
|         // Smoothy only keep a percentage of its tokens available in reserve | ||||
|         uint256 poolReserveMakerAmount = ISmoothy(smoothyInfo.poolAddress).getBalance(uint256(toTokenIdx)) - | ||||
|                                          ISmoothy(smoothyInfo.poolAddress)._yBalances(uint256(toTokenIdx)); | ||||
|         (, , , uint256 decimals) = ISmoothy(smoothyInfo.poolAddress).getTokenStats(uint256(toTokenIdx)); | ||||
|         poolReserveMakerAmount = poolReserveMakerAmount/(10**(18-decimals)); | ||||
|  | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             (bool didSucceed, bytes memory resultData) = | ||||
|                 smoothyInfo.poolAddress.staticcall.gas(SMOOTHY_CALL_GAS)( | ||||
|                     abi.encodeWithSelector( | ||||
|                         smoothyInfo.sellQuoteFunctionSelector, | ||||
|                         fromTokenIdx, | ||||
|                         toTokenIdx, | ||||
|                         takerTokenAmounts[i] | ||||
|                     )); | ||||
|             uint256 buyAmount = 0; | ||||
|             if (didSucceed) { | ||||
|                 buyAmount = abi.decode(resultData, (uint256)); | ||||
|             } | ||||
|  | ||||
|             // Make sure the quoted buyAmount is available in the pool reserve | ||||
|             if (buyAmount >= poolReserveMakerAmount) { | ||||
|                 // Assign pool reserve amount for all higher samples to break early | ||||
|                 for (uint256 j = i; j < numSamples; j++) { | ||||
|                     makerTokenAmounts[j] = poolReserveMakerAmount; | ||||
|                 } | ||||
|                 break; | ||||
|             } else { | ||||
|                 makerTokenAmounts[i] = buyAmount; | ||||
|             } | ||||
|  | ||||
|             // Break early if there are 0 amounts | ||||
|             if (makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Smoothy. | ||||
|     /// @param smoothyInfo Smoothy information specific to this token pair. | ||||
|     /// @param fromTokenIdx Index of the taker token (what to sell). | ||||
|     /// @param toTokenIdx Index of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromSmoothy( | ||||
|         SmoothyInfo memory smoothyInfo, | ||||
|         int128 fromTokenIdx, | ||||
|         int128 toTokenIdx, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         // Buys not supported so approximate it. | ||||
|         return _sampleApproximateBuys( | ||||
|             ApproximateBuyQuoteOpts({ | ||||
|                 makerTokenData: abi.encode(toTokenIdx, smoothyInfo), | ||||
|                 takerTokenData: abi.encode(fromTokenIdx, smoothyInfo), | ||||
|                 getSellQuoteCallback: _sampleSellForApproximateBuyFromSmoothy | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function _sampleSellForApproximateBuyFromSmoothy( | ||||
|         bytes memory takerTokenData, | ||||
|         bytes memory makerTokenData, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 buyAmount) | ||||
|     { | ||||
|         (int128 takerTokenIdx, SmoothyInfo memory smoothyInfo) = | ||||
|             abi.decode(takerTokenData, (int128, SmoothyInfo)); | ||||
|         (int128 makerTokenIdx) = | ||||
|             abi.decode(makerTokenData, (int128)); | ||||
|         (bool success, bytes memory resultData) = | ||||
|             address(this).staticcall(abi.encodeWithSelector( | ||||
|                 this.sampleSellsFromSmoothy.selector, | ||||
|                 smoothyInfo, | ||||
|                 takerTokenIdx, | ||||
|                 makerTokenIdx, | ||||
|                 _toSingleValueArray(sellAmount) | ||||
|             )); | ||||
|         if (!success) { | ||||
|             return 0; | ||||
|         } | ||||
|         // solhint-disable-next-line indent | ||||
|         return abi.decode(resultData, (uint256[]))[0]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										412
									
								
								packages/asset-swapper/contracts/src/SwapRevertSampler.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								packages/asset-swapper/contracts/src/SwapRevertSampler.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,412 @@ | ||||
|  | ||||
|  | ||||
| // 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; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./GasOverhead.sol"; | ||||
| import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; | ||||
| import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; | ||||
|  | ||||
| interface IHackedERC20 { | ||||
|     function _setBalance(address owner, uint256 amount) external; | ||||
|     function _setEnabled(bool enabled) external; | ||||
| } | ||||
|  | ||||
| contract SwapRevertSampler { | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     /// @dev Fixed address to register and read Gas overhead introduced by Swap revert sampling | ||||
|     GasOverhead private constant GAS_OVERHEAD = GasOverhead(0xDeF1000000000000000000000000000000001337); | ||||
|     /// @dev Maximum approximate (positive) error rate when approximating a buy quote. | ||||
|     uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4; | ||||
|     /// @dev Maximum iterations to perform when approximating a buy quote. | ||||
|     uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 3; | ||||
|     uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4; | ||||
|     /// @dev Upper limit of gas to give to a single Swap call | ||||
|     uint256 private constant CALL_STIPEND = 2e6; | ||||
|  | ||||
|  | ||||
|     // solhint-disable no-empty-blocks | ||||
|     /// @dev Payable fallback to receive ETH from Kyber/WETH. | ||||
|     receive () | ||||
|         external | ||||
|         payable | ||||
|     { } | ||||
|  | ||||
|     struct SwapRevertSamplerQuoteOpts { | ||||
|         // Address of the token which is being sold | ||||
|         address sellToken; | ||||
|         // Address of the token which is wanted | ||||
|         address buyToken; | ||||
|         // Data required for the bridge to execute the swap | ||||
|         bytes bridgeData; | ||||
|         // Callback to retrieve a swap quote. | ||||
|         function (address sellToken, address buyToken, bytes memory bridgeData, uint256 sellAmount) | ||||
|             external | ||||
|             returns (uint256) | ||||
|             getSwapQuoteCallback; | ||||
|     } | ||||
|  | ||||
|     struct SwapRevertSamplerBuyQuoteOpts { | ||||
|         // Address of the token which is being sold | ||||
|         address sellToken; | ||||
|         // Address of the token which is wanted | ||||
|         address buyToken; | ||||
|         // Data required for the bridge to execute the SELL_TOKEN->BUY_TOKEN swap | ||||
|         bytes sellTokenData; | ||||
|         // Data required for the bridge to execute the BUY_TOKEN->SELL_TOKEN swap | ||||
|         bytes buyTokenData; | ||||
|         // Callback to retrieve a swap quote. | ||||
|         function (address sellToken, address buyToken, bytes memory bridgeData, uint256 sellAmount) | ||||
|             external | ||||
|             returns (uint256) | ||||
|             getSwapQuoteCallback; | ||||
|     } | ||||
|  | ||||
|     function _callRevert( | ||||
|         bytes4 selector, | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 amountIn | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         // Clear any registered overhead | ||||
|         try | ||||
|             GAS_OVERHEAD.clearOverhead() | ||||
|         { } catch { } | ||||
|         // Measure the gas | ||||
|         uint256 gasUsed = gasleft(); | ||||
|         // Perform the sell | ||||
|         (bool success, bytes memory data) = address(this).call( | ||||
|             abi.encodeWithSelector(selector, sellToken, buyToken, bridgeData, amountIn) | ||||
|         ); | ||||
|         gasUsed = gasUsed - gasleft(); | ||||
|         // Remove any registered gas overhead | ||||
|         try | ||||
|             GAS_OVERHEAD.overhead() | ||||
|             returns (uint256 gasOverhead) | ||||
|         { | ||||
|             gasUsed = gasUsed - gasOverhead; | ||||
|         } catch { } | ||||
|  | ||||
|         if (!success) { | ||||
|             data.rrevert(); | ||||
|         } | ||||
|         // Revert with the amount bought | ||||
|         _revertSingleSwapSample(abi.decode(data, (uint256)), gasUsed); | ||||
|     } | ||||
|  | ||||
|     /// @dev  Mints the sell token, then performs the swap, then reverts with the amount out. | ||||
|     /// The SwapRevertSamplerQuoteOpts has been unrolled here as our ABI encoder cannot support | ||||
|     /// encoding the function | ||||
|     function _mintCallRevert( | ||||
|         bytes4 selector, | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256[] memory amountsIn | ||||
|     ) | ||||
|         external | ||||
|     { | ||||
|         // We assume the amounts are ascending and that | ||||
|         // the underlying call can handle selling a specific amount | ||||
|         uint256 amountIn = amountsIn[amountsIn.length - 1]; | ||||
|  | ||||
|         if (sellToken == address(_getNativeWrappedToken())) { | ||||
|             try | ||||
|                 IEtherTokenV06(payable(sellToken)).deposit{ value: amountIn }() | ||||
|             { } catch { } | ||||
|         } else { | ||||
|             IHackedERC20 hackedSellToken = IHackedERC20(payable(sellToken)); | ||||
|             // Enable sell token to be tracked and shadowed | ||||
|             try | ||||
|                 hackedSellToken._setEnabled(true) | ||||
|             { } catch { } | ||||
|  | ||||
|             // Mint enough to sell | ||||
|             try | ||||
|                 hackedSellToken._setBalance(address(this), amountIn) | ||||
|             { } catch { } | ||||
|         } | ||||
|  | ||||
|         // Burn any excess ETH to avoid balance issues for sources which use ETH directly | ||||
|         address(0).transfer(address(this).balance); | ||||
|  | ||||
|         uint256[] memory amountsOut = new uint256[](amountsIn.length); | ||||
|         uint256[] memory gasUsed = new uint256[](amountsIn.length); | ||||
|  | ||||
|         for (uint256 i = 0; i < amountsIn.length; i++) { | ||||
|             try | ||||
|                 this._callRevert{gas: CALL_STIPEND}( | ||||
|                     selector, | ||||
|                     sellToken, | ||||
|                     buyToken, | ||||
|                     bridgeData, | ||||
|                     amountsIn[i] | ||||
|                 ) | ||||
|             { | ||||
|                 require(false, "Swap Sample should have reverted"); | ||||
|             } catch (bytes memory reason) { | ||||
|                 // Parse the reverted sample data | ||||
|                 (amountsOut[i], gasUsed[i]) = _parseRevertedSingleSwapSample(reason); | ||||
|                 // If we detect the amount out is 0 then we return early | ||||
|                 // rather than continue performing excess work | ||||
|  | ||||
|                 // Some sources (Balancer) display issues, especially with small amounts | ||||
|                 // Where the amountsOut can range, e.g 448,0,0,0,2476,3048,0,4279,4941,0,0,7133, | ||||
|  | ||||
|                 if (amountsOut[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // Revert the entire sampling | ||||
|         _revertSwapSample(amountsOut, gasUsed); | ||||
|     } | ||||
|  | ||||
|     function _sampleSwapQuotesRevert( | ||||
|         SwapRevertSamplerQuoteOpts memory opts, | ||||
|         uint256[] memory amountsIn | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory amountsOut) | ||||
|     { | ||||
|         try | ||||
|             this._mintCallRevert( | ||||
|                 opts.getSwapQuoteCallback.selector, | ||||
|                 opts.sellToken, | ||||
|                 opts.buyToken, | ||||
|                 opts.bridgeData, | ||||
|                 amountsIn | ||||
|             ) | ||||
|         { | ||||
|             require(false, "Swap Sample should have reverted"); | ||||
|         } catch (bytes memory reason) { | ||||
|             // Parse the reverted sample datas | ||||
|             (amountsOut, gasUsed) = abi.decode(reason, (uint256[], uint256[])); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _getNativeWrappedToken() | ||||
|         internal | ||||
|         view | ||||
|         returns (IEtherTokenV06) | ||||
|     { | ||||
|         uint256 chainId; | ||||
|         assembly { | ||||
|             chainId := chainid() | ||||
|         } | ||||
|         address token; | ||||
|         if (chainId == 1) { | ||||
|             // Ethereum Mainnet | ||||
|             token = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | ||||
|         } else if (chainId == 3) { | ||||
|             // Ropsten | ||||
|             token = 0xc778417E063141139Fce010982780140Aa0cD5Ab; | ||||
|         } else if (chainId == 4) { | ||||
|             // Rinkeby | ||||
|             token = 0xc778417E063141139Fce010982780140Aa0cD5Ab; | ||||
|         } else if (chainId == 42) { | ||||
|             // Kovan | ||||
|             token = 0xd0A1E359811322d97991E03f863a0C30C2cF029C; | ||||
|         } else if (chainId == 56) { | ||||
|             // BSC  | ||||
|             token = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; | ||||
|         } else if (chainId == 137) { | ||||
|             // Polygon | ||||
|             token = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; | ||||
|         } else if (chainId == 1337) { | ||||
|             // 0x Ganache | ||||
|             token = 0x0B1ba0af832d7C05fD64161E0Db78E85978E8082; | ||||
|         } | ||||
|         if (token == address(0)) { | ||||
|             revert("No native wrapped token"); | ||||
|         } | ||||
|         return IEtherTokenV06(token); | ||||
|     } | ||||
|  | ||||
|     function _revertSingleSwapSample( | ||||
|         uint256 amount, | ||||
|         uint256 gasUsed | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         // Revert it so there is no state change | ||||
|         assembly { | ||||
|             mstore(0, amount) | ||||
|             mstore(32, gasUsed) | ||||
|             revert(0, 64) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _revertSwapSample( | ||||
|         uint256[] memory amounts, | ||||
|         uint256[] memory gasUsed | ||||
|     ) | ||||
|         internal | ||||
|     { | ||||
|         bytes memory data = abi.encode(amounts, gasUsed); | ||||
|         // Revert it so there is no state change | ||||
|         assembly { | ||||
|             revert(add(data, 32), mload(data)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Parses the reverted swap sample data. If no amount | ||||
|     ///      is decoded, 0 is returned. | ||||
|     /// @param reason the string which contains the possible | ||||
|     ///               sample amount | ||||
|     /// @return the decoded sample amount or 0 | ||||
|     /// @return the gas used in the sample | ||||
|     function _parseRevertedSingleSwapSample( | ||||
|         bytes memory reason | ||||
|     ) | ||||
|         internal | ||||
|         pure | ||||
|         returns (uint256, uint256) | ||||
|     { | ||||
|         if (reason.length != 64) { | ||||
|             return (0,0); | ||||
|         } | ||||
|         return abi.decode(reason, (uint256, uint256)); | ||||
|     } | ||||
|  | ||||
|     function _sampleSwapApproximateBuys( | ||||
|         SwapRevertSamplerBuyQuoteOpts memory opts, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         internal | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         if (makerTokenAmounts.length == 0) { | ||||
|             return (gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         takerTokenAmounts = new uint256[](makerTokenAmounts.length); | ||||
|         gasUsed = new uint256[](makerTokenAmounts.length); | ||||
|  | ||||
|         uint256[] memory sellAmounts = new uint256[](1); | ||||
|         sellAmounts[0] = makerTokenAmounts[0]; | ||||
|  | ||||
|         SwapRevertSamplerQuoteOpts memory sellOpts = SwapRevertSamplerQuoteOpts({ | ||||
|             sellToken: opts.sellToken, | ||||
|             buyToken: opts.buyToken, | ||||
|             bridgeData: opts.sellTokenData, | ||||
|             getSwapQuoteCallback: opts.getSwapQuoteCallback | ||||
|         }); | ||||
|  | ||||
|         SwapRevertSamplerQuoteOpts memory buyOpts = SwapRevertSamplerQuoteOpts({ | ||||
|             sellToken: opts.buyToken, | ||||
|             buyToken: opts.sellToken, | ||||
|             bridgeData: opts.buyTokenData, | ||||
|             getSwapQuoteCallback: opts.getSwapQuoteCallback | ||||
|         }); | ||||
|         // Inverted, perform a sell of the token the user wants to buy | ||||
|         (, sellAmounts) = _sampleSwapQuotesRevert(buyOpts, sellAmounts); | ||||
|         if (sellAmounts.length == 0 || sellAmounts[0] == 0) { | ||||
|             return (gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         uint256[] memory buyAmounts; | ||||
|         // Sell of the token the user wishes to dispose, see how much we buy | ||||
|         (, buyAmounts) = _sampleSwapQuotesRevert(sellOpts, sellAmounts); | ||||
|  | ||||
|         if (buyAmounts.length == 0 || buyAmounts[0] == 0) { | ||||
|             return (gasUsed, takerTokenAmounts); | ||||
|         } | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokenAmounts.length; i++) { | ||||
|             uint256[] memory _gasUsed; | ||||
|             for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) { | ||||
|                 // adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER | ||||
|                 sellAmounts[0] = _safeGetPartialAmountCeil( | ||||
|                     makerTokenAmounts[i], | ||||
|                     buyAmounts[0], | ||||
|                     sellAmounts[0] | ||||
|                 ); | ||||
|                 if (sellAmounts.length == 0 || sellAmounts[0] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 sellAmounts[0] = _safeGetPartialAmountCeil( | ||||
|                     (ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS), | ||||
|                     ONE_HUNDED_PERCENT_BPS, | ||||
|                     sellAmounts[0] | ||||
|                 ); | ||||
|                 if (sellAmounts.length == 0 || sellAmounts[0] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 uint256[] memory _buyAmounts; | ||||
|                 (_gasUsed, _buyAmounts) = _sampleSwapQuotesRevert(sellOpts, sellAmounts); | ||||
|                 if (_buyAmounts.length == 0 || _buyAmounts[0] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 // We re-use buyAmount next iteration, only assign if it is | ||||
|                 // non zero | ||||
|                 buyAmounts = _buyAmounts; | ||||
|                 // If we've reached our goal, exit early | ||||
|                 if (buyAmounts[0] >= makerTokenAmounts[i]) { | ||||
|                     uint256 eps = | ||||
|                         (buyAmounts[0] - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS / | ||||
|                         makerTokenAmounts[i]; | ||||
|                     if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             // We've encountered reverts, so bail | ||||
|             if (_gasUsed.length == 0 || _gasUsed[0] == 0) { | ||||
|                 return (gasUsed, takerTokenAmounts); | ||||
|             } | ||||
|  | ||||
|             if (buyAmounts.length > 0) { | ||||
|                 gasUsed[i] = _gasUsed[0]; | ||||
|                 // We do our best to close in on the requested amount, but we can either over buy or under buy and exit | ||||
|                 // if we hit a max iteration limit | ||||
|                 // We scale the sell amount to get the approximate target | ||||
|                 takerTokenAmounts[i] = _safeGetPartialAmountCeil( | ||||
|                     makerTokenAmounts[i], | ||||
|                     buyAmounts[0], | ||||
|                     sellAmounts[0] | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _safeGetPartialAmountCeil( | ||||
|         uint256 numerator, | ||||
|         uint256 denominator, | ||||
|         uint256 target | ||||
|     ) | ||||
|         internal | ||||
|         view | ||||
|         returns (uint256 partialAmount) | ||||
|     { | ||||
|         if (numerator == 0 || target == 0 || denominator == 0) return 0; | ||||
|         uint256 c = numerator * target; | ||||
|         if (c / numerator != target) return 0; | ||||
|         return (c + (denominator - 1)) / denominator; | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -40,10 +40,11 @@ contract TwoHopSampler { | ||||
|         returns ( | ||||
|             HopInfo memory firstHop, | ||||
|             HopInfo memory secondHop, | ||||
|             uint256 intermediateAssetAmount, | ||||
|             uint256 buyAmount | ||||
|         ) | ||||
|     { | ||||
|         uint256 intermediateAssetAmount = 0; | ||||
|         intermediateAssetAmount = 0; | ||||
|         for (uint256 i = 0; i != firstHopCalls.length; ++i) { | ||||
|             firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount); | ||||
|             (bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]); | ||||
| @@ -57,7 +58,7 @@ contract TwoHopSampler { | ||||
|             } | ||||
|         } | ||||
|         if (intermediateAssetAmount == 0) { | ||||
|             return (firstHop, secondHop, buyAmount); | ||||
|             return (firstHop, secondHop, intermediateAssetAmount, buyAmount); | ||||
|         } | ||||
|         for (uint256 j = 0; j != secondHopCalls.length; ++j) { | ||||
|             secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -20,32 +20,43 @@ | ||||
| pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IUniswapExchangeQuotes.sol"; | ||||
| import "./SamplerUtils.sol"; | ||||
|  | ||||
|  | ||||
| interface IUniswapExchangeFactory { | ||||
|  | ||||
|     /// @dev Get the exchange for a token. | ||||
|     /// @param tokenAddress The address of the token contract. | ||||
|     function getExchange(address tokenAddress) | ||||
|         external | ||||
|         view | ||||
|         returns (address); | ||||
| } | ||||
|  | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswap.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| contract UniswapSampler is | ||||
|     SamplerUtils | ||||
|     MixinUniswap, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Gas limit for Uniswap calls. | ||||
|     uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k | ||||
|  | ||||
|     constructor(IEtherTokenV06 weth) | ||||
|         public | ||||
|         MixinUniswap(weth) | ||||
|     { } | ||||
|  | ||||
|     function sampleSwapFromUniswap( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeUniswapInternal( | ||||
|             _getNativeWrappedToken(), | ||||
|             IERC20TokenV06(sellToken), | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from Uniswap. | ||||
|     /// @param router Address of the Uniswap Router | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromUniswap( | ||||
| @@ -55,59 +66,24 @@ contract UniswapSampler is | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken); | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             bool didSucceed = true; | ||||
|             if (makerToken == address(0)) { | ||||
|                 (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else if (takerToken == address(0)) { | ||||
|                 (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else { | ||||
|                 uint256 ethBought; | ||||
|                 (ethBought, didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthInputPrice.selector, | ||||
|                     takerTokenAmounts[i] | ||||
|                 ); | ||||
|                 if (ethBought != 0) { | ||||
|                     (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                         address(makerTokenExchange), | ||||
|                         makerTokenExchange.getEthToTokenInputPrice.selector, | ||||
|                         ethBought | ||||
|                     ); | ||||
|                 } else { | ||||
|                     makerTokenAmounts[i] = 0; | ||||
|                 } | ||||
|             } | ||||
|             // Break early if amounts are 0 | ||||
|             if (!didSucceed || makerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 bridgeData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromUniswap | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from Uniswap. | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param makerTokenAmounts Maker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromUniswap( | ||||
| @@ -117,98 +93,18 @@ contract UniswapSampler is | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         _assertValidPair(makerToken, takerToken); | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|  | ||||
|         IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken); | ||||
|         IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ? | ||||
|             IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             bool didSucceed = true; | ||||
|             if (makerToken == address(0)) { | ||||
|                 (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(takerTokenExchange), | ||||
|                     takerTokenExchange.getTokenToEthOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else if (takerToken == address(0)) { | ||||
|                 (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|             } else { | ||||
|                 uint256 ethSold; | ||||
|                 (ethSold, didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                     address(makerTokenExchange), | ||||
|                     makerTokenExchange.getEthToTokenOutputPrice.selector, | ||||
|                     makerTokenAmounts[i] | ||||
|                 ); | ||||
|                 if (ethSold != 0) { | ||||
|                     (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction( | ||||
|                         address(takerTokenExchange), | ||||
|                         takerTokenExchange.getTokenToEthOutputPrice.selector, | ||||
|                         ethSold | ||||
|                     ); | ||||
|                 } else { | ||||
|                     takerTokenAmounts[i] = 0; | ||||
|                 } | ||||
|             } | ||||
|             // Break early if amounts are 0 | ||||
|             if (!didSucceed || takerTokenAmounts[i] == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Gracefully calls a Uniswap pricing function. | ||||
|     /// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange. | ||||
|     /// @param functionSelector Selector of the target function. | ||||
|     /// @param inputAmount Quantity parameter particular to the pricing function. | ||||
|     /// @return outputAmount The returned amount from the function call. Will be | ||||
|     ///         zero if the call fails or if `uniswapExchangeAddress` is zero. | ||||
|     function _callUniswapExchangePriceFunction( | ||||
|         address uniswapExchangeAddress, | ||||
|         bytes4 functionSelector, | ||||
|         uint256 inputAmount | ||||
|     ) | ||||
|         private | ||||
|         view | ||||
|         returns (uint256 outputAmount, bool didSucceed) | ||||
|     { | ||||
|         if (uniswapExchangeAddress == address(0)) { | ||||
|             return (outputAmount, didSucceed); | ||||
|         } | ||||
|         bytes memory resultData; | ||||
|         (didSucceed, resultData) = | ||||
|             uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)( | ||||
|                 abi.encodeWithSelector( | ||||
|                     functionSelector, | ||||
|                     inputAmount | ||||
|                 )); | ||||
|         if (didSucceed) { | ||||
|             outputAmount = abi.decode(resultData, (uint256)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Retrive an existing Uniswap exchange contract. | ||||
|     ///      Throws if the exchange does not exist. | ||||
|     /// @param router Address of the Uniswap router. | ||||
|     /// @param tokenAddress Address of the token contract. | ||||
|     /// @return exchange `IUniswapExchangeQuotes` for the token. | ||||
|     function _getUniswapExchange(address router, address tokenAddress) | ||||
|         private | ||||
|         view | ||||
|         returns (IUniswapExchangeQuotes exchange) | ||||
|     { | ||||
|         exchange = IUniswapExchangeQuotes( | ||||
|             address(IUniswapExchangeFactory(router) | ||||
|             .getExchange(tokenAddress)) | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: takerToken, | ||||
|                 buyToken: makerToken, | ||||
|                 sellTokenData: abi.encode(router), | ||||
|                 buyTokenData: abi.encode(router), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromUniswap | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|   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. | ||||
| @@ -21,17 +21,36 @@ pragma solidity ^0.6; | ||||
| pragma experimental ABIEncoderV2; | ||||
|  | ||||
| import "./interfaces/IUniswapV2Router01.sol"; | ||||
| import "@0x/contracts-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV2.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
|  | ||||
| contract UniswapV2Sampler | ||||
| contract UniswapV2Sampler is | ||||
|     MixinUniswapV2, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Gas limit for UniswapV2 calls. | ||||
|     uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k | ||||
|  | ||||
|     function sampleSwapFromUniswapV2( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeUniswapV2( | ||||
|             IERC20TokenV06(buyToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from UniswapV2. | ||||
|     /// @param router Router to look up tokens and amounts | ||||
|     /// @param path Token route. Should be takerToken -> makerToken | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return gasUsed gas consumed for each sample amount | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromUniswapV2( | ||||
| @@ -40,34 +59,24 @@ contract UniswapV2Sampler | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory makerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory makerTokenAmounts) | ||||
|     { | ||||
|         uint256 numSamples = takerTokenAmounts.length; | ||||
|         makerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IUniswapV2Router01(router).getAmountsOut | ||||
|                     {gas: UNISWAPV2_CALL_GAS} | ||||
|                     (takerTokenAmounts[i], path) | ||||
|                 returns (uint256[] memory amounts) | ||||
|             { | ||||
|                 makerTokenAmounts[i] = amounts[path.length - 1]; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (makerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         (gasUsed, makerTokenAmounts) = _sampleSwapQuotesRevert( | ||||
|             SwapRevertSamplerQuoteOpts({ | ||||
|                 sellToken: path[0], | ||||
|                 buyToken: path[path.length - 1], | ||||
|                 bridgeData: abi.encode(router, path), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromUniswapV2 | ||||
|             }), | ||||
|             takerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from UniswapV2. | ||||
|     /// @param router Router to look up tokens and amounts | ||||
|     /// @param path Token route. Should be takerToken -> makerToken. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return gasUsed gas consumed for each sample amount | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromUniswapV2( | ||||
| @@ -76,27 +85,22 @@ contract UniswapV2Sampler | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         view | ||||
|         returns (uint256[] memory takerTokenAmounts) | ||||
|         returns (uint256[] memory gasUsed, uint256[] memory takerTokenAmounts) | ||||
|     { | ||||
|         uint256 numSamples = makerTokenAmounts.length; | ||||
|         takerTokenAmounts = new uint256[](numSamples); | ||||
|         for (uint256 i = 0; i < numSamples; i++) { | ||||
|             try | ||||
|                 IUniswapV2Router01(router).getAmountsIn | ||||
|                     {gas: UNISWAPV2_CALL_GAS} | ||||
|                     (makerTokenAmounts[i], path) | ||||
|                 returns (uint256[] memory amounts) | ||||
|             { | ||||
|                 takerTokenAmounts[i] = amounts[0]; | ||||
|                 // Break early if there are 0 amounts | ||||
|                 if (takerTokenAmounts[i] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } catch (bytes memory) { | ||||
|                 // Swallow failures, leaving all results as zero. | ||||
|                 break; | ||||
|             } | ||||
|         address[] memory reversedPath = new address[](path.length); | ||||
|         for (uint256 i = 0; i < path.length; ++i) { | ||||
|             reversedPath[i] = path[path.length - i - 1]; | ||||
|         } | ||||
|  | ||||
|         (gasUsed, takerTokenAmounts) = _sampleSwapApproximateBuys( | ||||
|             SwapRevertSamplerBuyQuoteOpts({ | ||||
|                 sellToken: path[0], | ||||
|                 buyToken: path[path.length - 1], | ||||
|                 sellTokenData: abi.encode(router, path), | ||||
|                 buyTokenData: abi.encode(router, reversedPath), | ||||
|                 getSwapQuoteCallback: this.sampleSwapFromUniswapV2 | ||||
|             }), | ||||
|             makerTokenAmounts | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,18 +21,16 @@ pragma solidity ^0.6; | ||||
| 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-zero-ex/contracts/src/transformers/bridges/mixins/MixinUniswapV3.sol"; | ||||
| import "./SwapRevertSampler.sol"; | ||||
|  | ||||
| interface IUniswapV3Quoter { | ||||
|     function factory() | ||||
|         external | ||||
|         view | ||||
|         returns (IUniswapV3Factory factory); | ||||
|     function quoteExactInput(bytes memory path, uint256 amountIn) | ||||
|         external | ||||
|         returns (uint256 amountOut); | ||||
|     function quoteExactOutput(bytes memory path, uint256 amountOut) | ||||
|         external | ||||
|         returns (uint256 amountIn); | ||||
| } | ||||
|  | ||||
| interface IUniswapV3Factory { | ||||
| @@ -48,26 +46,47 @@ interface IUniswapV3Pool { | ||||
|     function fee() external view returns (uint24); | ||||
| } | ||||
|  | ||||
| contract UniswapV3Sampler | ||||
| contract UniswapV3Sampler is | ||||
|     MixinUniswapV3, | ||||
|     SwapRevertSampler | ||||
| { | ||||
|     /// @dev Gas limit for UniswapV3 calls. This is 100% a guess. | ||||
|     uint256 constant private QUOTE_GAS = 300e3; | ||||
|     using LibRichErrorsV06 for bytes; | ||||
|  | ||||
|     function sampleSwapFromUniswapV3( | ||||
|         address sellToken, | ||||
|         address buyToken, | ||||
|         bytes memory bridgeData, | ||||
|         uint256 takerTokenAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256) | ||||
|     { | ||||
|         return _tradeUniswapV3( | ||||
|             IERC20TokenV06(sellToken), | ||||
|             takerTokenAmount, | ||||
|             bridgeData | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample sell quotes from UniswapV3. | ||||
|     /// @param quoter UniswapV3 Quoter contract. | ||||
|     /// @param router UniswapV3 Router contract. | ||||
|     /// @param path Token route. Should be takerToken -> makerToken | ||||
|     /// @param takerTokenAmounts Taker token sell amount for each sample. | ||||
|     /// @return uniswapPaths The encoded uniswap path for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return makerTokenAmounts Maker amounts bought at each taker token | ||||
|     ///         amount. | ||||
|     function sampleSellsFromUniswapV3( | ||||
|         IUniswapV3Quoter quoter, | ||||
|         address router, | ||||
|         IERC20TokenV06[] memory path, | ||||
|         uint256[] memory takerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns ( | ||||
|             bytes[] memory uniswapPaths, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory makerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
| @@ -75,50 +94,57 @@ contract UniswapV3Sampler | ||||
|             _getValidPoolPaths(quoter.factory(), path, 0); | ||||
|  | ||||
|         makerTokenAmounts = new uint256[](takerTokenAmounts.length); | ||||
|         gasUsed = new uint256[](takerTokenAmounts.length); | ||||
|         uniswapPaths = new bytes[](takerTokenAmounts.length); | ||||
|  | ||||
|         for (uint256 i = 0; i < takerTokenAmounts.length; ++i) { | ||||
|             // Pick the best result from all the paths. | ||||
|             bytes memory topUniswapPath; | ||||
|             uint256 topBuyAmount = 0; | ||||
|             for (uint256 j = 0; j < poolPaths.length; ++j) { | ||||
|                 bytes memory uniswapPath = _toUniswapPath(path, poolPaths[j]); | ||||
|                 try | ||||
|                     quoter.quoteExactInput | ||||
|                         { gas: QUOTE_GAS } | ||||
|                         (uniswapPath, takerTokenAmounts[i]) | ||||
|                         returns (uint256 buyAmount) | ||||
|                 { | ||||
|                     if (topBuyAmount <= buyAmount) { | ||||
|                         topBuyAmount = buyAmount; | ||||
|                         topUniswapPath = uniswapPath; | ||||
|                     } | ||||
|                 } catch { } | ||||
|         for (uint256 i = 0; i < poolPaths.length; ++i) { | ||||
|             bytes memory _uniswapPath = _toUniswapPath(path, poolPaths[i]); | ||||
|             ( | ||||
|                 uint256[] memory _gasUsed, | ||||
|                 uint256[] memory _makerTokenAmounts | ||||
|             ) = _sampleSwapQuotesRevert( | ||||
|                 SwapRevertSamplerQuoteOpts({ | ||||
|                     sellToken: address(path[0]), | ||||
|                     buyToken: address(path[path.length - 1]), | ||||
|                     bridgeData: abi.encode(router, _uniswapPath), | ||||
|                     getSwapQuoteCallback: this.sampleSwapFromUniswapV3 | ||||
|                 }), | ||||
|                 takerTokenAmounts | ||||
|             ); | ||||
|             for (uint256 j = 0; j < _makerTokenAmounts.length; ++j) { | ||||
|                 // Break early if we can't complete the sells. | ||||
|                 if (_makerTokenAmounts[j] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 // If this is better than what we have found, prefer it | ||||
|                 if (makerTokenAmounts[j] <= _makerTokenAmounts[j]) { | ||||
|                     makerTokenAmounts[j] = _makerTokenAmounts[j]; | ||||
|                     gasUsed[j] = _gasUsed[j]; | ||||
|                     uniswapPaths[j] = _uniswapPath; | ||||
|                 } | ||||
|             } | ||||
|             // Break early if we can't complete the buys. | ||||
|             if (topBuyAmount == 0) { | ||||
|                 break; | ||||
|             } | ||||
|             makerTokenAmounts[i] = topBuyAmount; | ||||
|             uniswapPaths[i] = topUniswapPath; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// @dev Sample buy quotes from UniswapV3. | ||||
|     /// @param quoter UniswapV3 Quoter contract. | ||||
|     /// @param router UniswapV3 Router contract. | ||||
|     /// @param path Token route. Should be takerToken -> makerToken. | ||||
|     /// @param makerTokenAmounts Maker token buy amount for each sample. | ||||
|     /// @return uniswapPaths The encoded uniswap path for each sample. | ||||
|     /// @return gasUsed gas consumed in each sample sell | ||||
|     /// @return takerTokenAmounts Taker amounts sold at each maker token | ||||
|     ///         amount. | ||||
|     function sampleBuysFromUniswapV3( | ||||
|         IUniswapV3Quoter quoter, | ||||
|         address router, | ||||
|         IERC20TokenV06[] memory path, | ||||
|         uint256[] memory makerTokenAmounts | ||||
|     ) | ||||
|         public | ||||
|         returns ( | ||||
|             bytes[] memory uniswapPaths, | ||||
|             uint256[] memory gasUsed, | ||||
|             uint256[] memory takerTokenAmounts | ||||
|         ) | ||||
|     { | ||||
| @@ -127,37 +153,43 @@ contract UniswapV3Sampler | ||||
|         IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path); | ||||
|  | ||||
|         takerTokenAmounts = new uint256[](makerTokenAmounts.length); | ||||
|         gasUsed = new uint256[](makerTokenAmounts.length); | ||||
|         uniswapPaths = new bytes[](makerTokenAmounts.length); | ||||
|  | ||||
|         for (uint256 i = 0; i < makerTokenAmounts.length; ++i) { | ||||
|             // Pick the best result from all the paths. | ||||
|             bytes memory topUniswapPath; | ||||
|             uint256 topSellAmount = 0; | ||||
|             for (uint256 j = 0; j < poolPaths.length; ++j) { | ||||
|                 // quoter requires path to be reversed for buys. | ||||
|                 bytes memory uniswapPath = _toUniswapPath( | ||||
|                     reversedPath, | ||||
|                     _reversePoolPath(poolPaths[j]) | ||||
|                 ); | ||||
|                 try | ||||
|                     quoter.quoteExactOutput | ||||
|                         { gas: QUOTE_GAS } | ||||
|                         (uniswapPath, makerTokenAmounts[i]) | ||||
|                         returns (uint256 sellAmount) | ||||
|                 { | ||||
|                     if (topSellAmount == 0 || topSellAmount >= sellAmount) { | ||||
|                         topSellAmount = sellAmount; | ||||
|                         // But the output path should still be encoded for sells. | ||||
|                         topUniswapPath = _toUniswapPath(path, poolPaths[j]); | ||||
|                     } | ||||
|                 } catch {} | ||||
|         for (uint256 i = 0; i < poolPaths.length; ++i) { | ||||
|             ( | ||||
|                 uint256[] memory _gasUsed, | ||||
|                 uint256[] memory _takerTokenAmounts | ||||
|             ) = _sampleSwapApproximateBuys( | ||||
|                 SwapRevertSamplerBuyQuoteOpts({ | ||||
|                     sellToken: address(path[0]), | ||||
|                     buyToken: address(path[path.length - 1]), | ||||
|                     sellTokenData: abi.encode(router, _toUniswapPath(path, poolPaths[i])), | ||||
|                     buyTokenData: abi.encode( | ||||
|                         router, | ||||
|                         _toUniswapPath( | ||||
|                             reversedPath, | ||||
|                             _reversePoolPath(poolPaths[i]) | ||||
|                         ) | ||||
|                     ), | ||||
|                     getSwapQuoteCallback: this.sampleSwapFromUniswapV3 | ||||
|                 }), | ||||
|                 makerTokenAmounts | ||||
|             ); | ||||
|  | ||||
|             for (uint256 j = 0; j < _takerTokenAmounts.length; ++j) { | ||||
|                 // Break early if we can't complete the buys. | ||||
|                 if (_takerTokenAmounts[j] == 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 // We can go from high to low here | ||||
|                 if (takerTokenAmounts[j] == 0 || takerTokenAmounts[j] >= _takerTokenAmounts[j]) { | ||||
|                     takerTokenAmounts[j] = _takerTokenAmounts[j]; | ||||
|                     gasUsed[j] = _gasUsed[j]; | ||||
|                     // But the output path should still be encoded for sells. | ||||
|                     uniswapPaths[j] = _toUniswapPath(path, poolPaths[i]); | ||||
|                 } | ||||
|             } | ||||
|             // Break early if we can't complete the buys. | ||||
|             if (topSellAmount == 0) { | ||||
|                 break; | ||||
|             } | ||||
|             takerTokenAmounts[i] = topSellAmount; | ||||
|             uniswapPaths[i] = topUniswapPath; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -268,7 +300,7 @@ contract UniswapV3Sampler | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         // Must have a balance of both tokens. | ||||
|         // // Must have a balance of both tokens. | ||||
|         if (pool.token0().balanceOf(address(pool)) == 0) { | ||||
|             return false; | ||||
|         } | ||||
|   | ||||
| @@ -77,4 +77,24 @@ contract UtilitySampler { | ||||
|         assembly { size := extcodesize(account) } | ||||
|         return size > 0; | ||||
|     } | ||||
|  | ||||
|     function getCode(address addr) | ||||
|         public | ||||
|         view | ||||
|         returns (bytes memory code) | ||||
|     { | ||||
|         assembly { | ||||
|             // retrieve the size of the code, this needs assembly | ||||
|             let size := extcodesize(addr) | ||||
|             // allocate output byte array - this could also be done without assembly | ||||
|             // by using o_code = new bytes(size) | ||||
|             code := mload(0x40) | ||||
|             // new "memory end" including padding | ||||
|             mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) | ||||
|             // store length in memory | ||||
|             mstore(code, size) | ||||
|             // actually retrieve the code, this needs assembly | ||||
|             extcodecopy(addr, add(code, 0x20), 0, size) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,44 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IBalancer { | ||||
|     function isBound(address t) external view returns (bool); | ||||
|     function getDenormalizedWeight(address token) external view returns (uint256); | ||||
|     function getBalance(address token) external view returns (uint256); | ||||
|     function getSwapFee() external view returns (uint256); | ||||
|     function calcOutGivenIn( | ||||
|         uint256 tokenBalanceIn, | ||||
|         uint256 tokenWeightIn, | ||||
|         uint256 tokenBalanceOut, | ||||
|         uint256 tokenWeightOut, | ||||
|         uint256 tokenAmountIn, | ||||
|         uint256 swapFee | ||||
|     ) external pure returns (uint256 tokenAmountOut); | ||||
|     function calcInGivenOut( | ||||
|         uint256 tokenBalanceIn, | ||||
|         uint256 tokenWeightIn, | ||||
|         uint256 tokenBalanceOut, | ||||
|         uint256 tokenWeightOut, | ||||
|         uint256 tokenAmountOut, | ||||
|         uint256 swapFee | ||||
|     ) external pure returns (uint256 tokenAmountIn); | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IBancor {} | ||||
|  | ||||
| interface IBancorNetwork { | ||||
|   function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory); | ||||
|   function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256); | ||||
| } | ||||
|  | ||||
| interface IBancorRegistry { | ||||
|     function getAddress(bytes32 _contractName) external view returns (address); | ||||
|     function BANCOR_NETWORK() external view returns (bytes32); | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| // solhint-disable func-name-mixedcase | ||||
| interface ICurve { | ||||
|  | ||||
|     /// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token. | ||||
|     ///      This function exists on later versions of Curve (USDC/DAI/USDT) | ||||
|     /// @param i The token index being sold. | ||||
|     /// @param j The token index being bought. | ||||
|     /// @param sellAmount The amount of token being bought. | ||||
|     /// @param minBuyAmount The minimum buy amount of the token being bought. | ||||
|     function exchange_underlying( | ||||
|         int128 i, | ||||
|         int128 j, | ||||
|         uint256 sellAmount, | ||||
|         uint256 minBuyAmount | ||||
|     ) | ||||
|         external; | ||||
|  | ||||
|     /// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken` | ||||
|     /// @param i The token index being sold. | ||||
|     /// @param j The token index being bought. | ||||
|     /// @param sellAmount The amount of token being bought. | ||||
|     function get_dy_underlying( | ||||
|         int128 i, | ||||
|         int128 j, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256 dy); | ||||
|  | ||||
|     /// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken` | ||||
|     ///      This function exists on later versions of Curve (USDC/DAI/USDT) | ||||
|     /// @param i The token index being sold. | ||||
|     /// @param j The token index being bought. | ||||
|     /// @param buyAmount The amount of token being bought. | ||||
|     function get_dx_underlying( | ||||
|         int128 i, | ||||
|         int128 j, | ||||
|         uint256 buyAmount | ||||
|     ) | ||||
|         external | ||||
|         returns (uint256 dx); | ||||
|  | ||||
|     /// @dev Get the underlying token address from the token index | ||||
|     /// @param i The token index. | ||||
|     function underlying_coins( | ||||
|         int128 i | ||||
|     ) | ||||
|         external | ||||
|         returns (address tokenAddress); | ||||
| } | ||||
| @@ -22,22 +22,6 @@ pragma solidity ^0.6; | ||||
| // Keepin everything together | ||||
| interface IKyberNetwork { | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| interface IKyberNetworkProxy { | ||||
|  | ||||
|     function getExpectedRateAfterFee( | ||||
|         address src, | ||||
|         address dest, | ||||
|         uint256 srcQty, | ||||
|         uint256 platformFeeBps, | ||||
|         bytes calldata hint | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 expectedRate); | ||||
| } | ||||
|  | ||||
| interface IKyberHintHandler { | ||||
|   | ||||
| @@ -1,33 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IMStable { | ||||
|  | ||||
|     function getSwapOutput( | ||||
|         address _input, | ||||
|         address _output, | ||||
|         uint256 _quantity | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 swapOutput); | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IMooniswapRegistry { | ||||
|  | ||||
|     function pools(address token1, address token2) external view returns(address); | ||||
| } | ||||
|  | ||||
| interface IMooniswap { | ||||
|  | ||||
|     function getReturn( | ||||
|         address fromToken, | ||||
|         address destToken, | ||||
|         uint256 amount | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns(uint256 returnAmount); | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IMultiBridge { | ||||
|  | ||||
|     /// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`. | ||||
|     /// @param tokenAddress The address of the ERC20 token to transfer. | ||||
|     /// @param from Address to transfer asset from. | ||||
|     /// @param to Address to transfer asset to. | ||||
|     /// @param amount Amount of asset to transfer. | ||||
|     /// @param bridgeData Arbitrary asset data needed by the bridge contract. | ||||
|     /// @return success The magic bytes `0xdc1600f3` if successful. | ||||
|     function bridgeTransferFrom( | ||||
|         address tokenAddress, | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 amount, | ||||
|         bytes calldata bridgeData | ||||
|     ) | ||||
|         external | ||||
|         returns (bytes4 success); | ||||
|  | ||||
|     /// @dev Quotes the amount of `makerToken` that would be obtained by | ||||
|     ///      selling `sellAmount` of `takerToken`. | ||||
|     /// @param takerToken Address of the taker token (what to sell). | ||||
|     /// @param intermediateToken The address of the intermediate token to | ||||
|     ///        use in an indirect route. | ||||
|     /// @param makerToken Address of the maker token (what to buy). | ||||
|     /// @param sellAmount Amount of `takerToken` to sell. | ||||
|     /// @return makerTokenAmount Amount of `makerToken` that would be obtained. | ||||
|     function getSellQuote( | ||||
|         address takerToken, | ||||
|         address intermediateToken, | ||||
|         address makerToken, | ||||
|         uint256 sellAmount | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 makerTokenAmount); | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| /* | ||||
|  | ||||
|   Copyright 2020 ZeroEx Intl. | ||||
|  | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
|  | ||||
| */ | ||||
|  | ||||
| pragma solidity ^0.6; | ||||
|  | ||||
|  | ||||
| interface IShell { | ||||
|  | ||||
|     function viewOriginSwap ( | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 fromAmount | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 toAmount); | ||||
|  | ||||
|     function viewTargetSwap ( | ||||
|         address from, | ||||
|         address to, | ||||
|         uint256 toAmount | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 fromAmount); | ||||
| } | ||||
|  | ||||
| @@ -1,45 +0,0 @@ | ||||
| // 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; | ||||
|  | ||||
|  | ||||
| interface ISmoothy { | ||||
|  | ||||
|     function getBalance ( | ||||
|         uint256 tid | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 balance); | ||||
|  | ||||
|     function _yBalances ( | ||||
|         uint256 tid | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 balance); | ||||
|  | ||||
|     function getTokenStats ( | ||||
|         uint256 tid | ||||
|     ) | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals); | ||||
| } | ||||
| @@ -127,7 +127,6 @@ contract FailTrigger { | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     IUniswapExchangeQuotes, | ||||
|     TestDeploymentConstants, | ||||
|     FailTrigger | ||||
| { | ||||
| @@ -145,7 +144,6 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     function getEthToTokenInputPrice( | ||||
|         uint256 ethSold | ||||
|     ) | ||||
|         override | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 tokensBought) | ||||
| @@ -163,7 +161,6 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     function getEthToTokenOutputPrice( | ||||
|         uint256 tokensBought | ||||
|     ) | ||||
|         override | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 ethSold) | ||||
| @@ -181,7 +178,6 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     function getTokenToEthInputPrice( | ||||
|         uint256 tokensSold | ||||
|     ) | ||||
|         override | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 ethBought) | ||||
| @@ -199,7 +195,6 @@ contract TestERC20BridgeSamplerUniswapExchange is | ||||
|     function getTokenToEthOutputPrice( | ||||
|         uint256 ethBought | ||||
|     ) | ||||
|         override | ||||
|         external | ||||
|         view | ||||
|         returns (uint256 tokensSold) | ||||
| @@ -376,6 +371,31 @@ contract TestERC20BridgeSamplerKyberNetwork is | ||||
|             toToken | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     function tradeWithHint( | ||||
|         address fromToken, | ||||
|         uint256 sellAmount, | ||||
|         address toToken, | ||||
|         address payable recipientAddress, | ||||
|         uint256 maxBuyTokenAmount, | ||||
|         uint256 minConversionRate, | ||||
|         address payable walletId, | ||||
|         bytes calldata hint | ||||
|     ) | ||||
|         external | ||||
|         payable | ||||
|         returns (uint256 boughtAmount) | ||||
|     { | ||||
|         _revertIfShouldFail(); | ||||
|         fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken; | ||||
|         toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken; | ||||
|         return LibDeterministicQuotes.getDeterministicSellQuote( | ||||
|             SALT, | ||||
|             fromToken, | ||||
|             toToken, | ||||
|             sellAmount | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -427,47 +447,17 @@ contract TestERC20BridgeSamplerEth2Dai is | ||||
| } | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSamplerUniswapExchangeFactory is | ||||
|     IUniswapExchangeFactory | ||||
| { | ||||
|     mapping (address => IUniswapExchangeQuotes) private _exchangesByToken; | ||||
|  | ||||
|     // Creates Uniswap exchange contracts for tokens. | ||||
|     function createTokenExchanges(address[] calldata tokenAddresses) | ||||
|         external | ||||
|     { | ||||
|         for (uint256 i = 0; i < tokenAddresses.length; i++) { | ||||
|             address tokenAddress = tokenAddresses[i]; | ||||
|             _exchangesByToken[tokenAddress] = | ||||
|                 new TestERC20BridgeSamplerUniswapExchange(tokenAddress); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // `IUniswapExchangeFactory.getExchange()`. | ||||
|     function getExchange(address tokenAddress) | ||||
|         override | ||||
|         external | ||||
|         view | ||||
|         returns (address) | ||||
|     { | ||||
|         return address(_exchangesByToken[tokenAddress]); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| contract TestERC20BridgeSampler is | ||||
|     ERC20BridgeSampler, | ||||
|     FailTrigger | ||||
| { | ||||
|     TestERC20BridgeSamplerUniswapExchangeFactory public uniswap; | ||||
|     TestERC20BridgeSamplerUniswapV2Router01 public uniswapV2Router; | ||||
|     TestERC20BridgeSamplerEth2Dai public eth2Dai; | ||||
|     TestERC20BridgeSamplerKyberNetwork public kyber; | ||||
|  | ||||
|     uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1; | ||||
|  | ||||
|     constructor() public ERC20BridgeSampler() { | ||||
|         uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory(); | ||||
|     constructor() public ERC20BridgeSampler(IEtherTokenV06(address(0))) { | ||||
|         uniswapV2Router = new TestERC20BridgeSamplerUniswapV2Router01(); | ||||
|         eth2Dai = new TestERC20BridgeSamplerEth2Dai(); | ||||
|         kyber = new TestERC20BridgeSamplerKyberNetwork(); | ||||
| @@ -477,7 +467,6 @@ contract TestERC20BridgeSampler is | ||||
|     function createTokenExchanges(address[] calldata tokenAddresses) | ||||
|         external | ||||
|     { | ||||
|         uniswap.createTokenExchanges(tokenAddresses); | ||||
|     } | ||||
|  | ||||
|     // Overridden to return deterministic states. | ||||
| @@ -493,14 +482,4 @@ contract TestERC20BridgeSampler is | ||||
|     { | ||||
|         return uint256(keccak256(abi.encode(order.salt))) % order.takerAmount; | ||||
|     } | ||||
|  | ||||
|     // Overriden to return deterministic decimals. | ||||
|     function _getTokenDecimals(address tokenAddress) | ||||
|         override | ||||
|         internal | ||||
|         view | ||||
|         returns (uint8 decimals) | ||||
|     { | ||||
|         return LibDeterministicQuotes.getDeterministicTokenDecimals(tokenAddress); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -37,9 +37,9 @@ | ||||
|         "sampler-size": "jq .compilerOutput.evm.deployedBytecode.object  -- test/generated-artifacts/ERC20BridgeSampler.json | echo $(( $(wc -c) / 2 - 1 ))" | ||||
|     }, | ||||
|     "config": { | ||||
|         "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker", | ||||
|         "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,DelegateHackedERC20,FakeTaker,HackedERC20,GasOverhead", | ||||
|         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", | ||||
|         "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", | ||||
|         "abis": "./test/generated-artifacts/@(BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|CurveV2Sampler|DODOSampler|DODOV2Sampler|DelegateHackedERC20|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|GasOverhead|HackedERC20|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|ShellSampler|SwapRevertSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", | ||||
|         "postpublish": { | ||||
|             "assets": [] | ||||
|         } | ||||
|   | ||||
| @@ -6,10 +6,16 @@ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json'; | ||||
| import * as DelegateHackedERC20 from '../generated-artifacts/DelegateHackedERC20.json'; | ||||
| import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json'; | ||||
| import * as FakeTaker from '../generated-artifacts/FakeTaker.json'; | ||||
| import * as GasOverhead from '../generated-artifacts/GasOverhead.json'; | ||||
| import * as HackedERC20 from '../generated-artifacts/HackedERC20.json'; | ||||
| export const artifacts = { | ||||
|     ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, | ||||
|     BalanceChecker: BalanceChecker as ContractArtifact, | ||||
|     DelegateHackedERC20: DelegateHackedERC20 as ContractArtifact, | ||||
|     FakeTaker: FakeTaker as ContractArtifact, | ||||
|     HackedERC20: HackedERC20 as ContractArtifact, | ||||
|     GasOverhead: GasOverhead as ContractArtifact, | ||||
| }; | ||||
|   | ||||
| @@ -47,7 +47,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = { | ||||
|     chainId: ChainId.Mainnet, | ||||
|     orderRefreshIntervalMs: 10000, // 10 seconds | ||||
|     ...DEFAULT_ORDER_PRUNER_OPTS, | ||||
|     samplerGasLimit: 500e6, | ||||
|     samplerGasLimit: 500e7, | ||||
|     ethGasStationUrl: ETH_GAS_STATION_API_URL, | ||||
|     rfqt: { | ||||
|         takerApiKeyWhitelist: [], | ||||
| @@ -91,8 +91,6 @@ export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) => | ||||
| const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||||
| export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r: EMPTY_BYTES32, s: EMPTY_BYTES32 }; | ||||
|  | ||||
| export { DEFAULT_FEE_SCHEDULE, DEFAULT_GAS_SCHEDULE } from './utils/market_operation_utils/constants'; | ||||
|  | ||||
| export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000); | ||||
|  | ||||
| // tslint:disable-next-line: custom-no-magic-numbers | ||||
|   | ||||
| @@ -116,7 +116,6 @@ export { | ||||
| export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; | ||||
| export { | ||||
|     DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID, | ||||
|     DEFAULT_GAS_SCHEDULE, | ||||
|     SOURCE_FLAGS, | ||||
|     BUY_SOURCE_FILTER_BY_CHAIN_ID, | ||||
|     SELL_SOURCE_FILTER_BY_CHAIN_ID, | ||||
| @@ -138,7 +137,6 @@ export { | ||||
|     DODOFillData, | ||||
|     ERC20BridgeSource, | ||||
|     ExchangeProxyOverhead, | ||||
|     FeeSchedule, | ||||
|     Fill, | ||||
|     FillData, | ||||
|     GetMarketOrdersRfqOpts, | ||||
|   | ||||
| @@ -27,13 +27,16 @@ import { | ||||
| import { assert } from './utils/assert'; | ||||
| import { MarketOperationUtils } from './utils/market_operation_utils'; | ||||
| import { BancorService } from './utils/market_operation_utils/bancor_service'; | ||||
| import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants'; | ||||
| import { | ||||
|     MAX_UINT256, | ||||
|     NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|     SOURCE_FLAGS, | ||||
|     ZERO_AMOUNT, | ||||
| } from './utils/market_operation_utils/constants'; | ||||
| import { DexOrderSampler } from './utils/market_operation_utils/sampler'; | ||||
| import { SourceFilters } from './utils/market_operation_utils/source_filters'; | ||||
| import { | ||||
|     ERC20BridgeSource, | ||||
|     FeeSchedule, | ||||
|     FillData, | ||||
|     GetMarketOrdersOpts, | ||||
|     MarketDepth, | ||||
|     MarketDepthSide, | ||||
| @@ -115,10 +118,13 @@ export class SwapQuoter { | ||||
|         // Allow the sampler bytecode to be overwritten using geths override functionality | ||||
|         const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object'); | ||||
|         // Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work | ||||
|         const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS; | ||||
|         // We default the sampler address to be FlashWallet to account for allowances being set on tokens | ||||
|         const samplerAddress = | ||||
|             (options.samplerOverrides && options.samplerOverrides.to) || | ||||
|             this._contractAddresses.exchangeProxyFlashWallet; | ||||
|         const defaultCodeOverrides = samplerBytecode | ||||
|             ? { | ||||
|                   [samplerAddress]: { code: samplerBytecode }, | ||||
|                   [samplerAddress]: { code: samplerBytecode, balance: MAX_UINT256 }, | ||||
|               } | ||||
|             : {}; | ||||
|         const samplerOverrides = _.assign( | ||||
| @@ -198,6 +204,7 @@ export class SwapQuoter { | ||||
|         const optimizerResults = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync( | ||||
|             allOrders, | ||||
|             makerTokenBuyAmounts, | ||||
|             gasPrice, | ||||
|             opts as GetMarketOrdersOpts, | ||||
|         ); | ||||
|  | ||||
| @@ -212,7 +219,6 @@ export class SwapQuoter { | ||||
|                         MarketOperation.Buy, | ||||
|                         makerTokenBuyAmounts[i], | ||||
|                         gasPrice, | ||||
|                         opts.gasSchedule, | ||||
|                         opts.bridgeSlippage, | ||||
|                     ); | ||||
|                 } else { | ||||
| @@ -268,6 +274,7 @@ export class SwapQuoter { | ||||
|                         output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount, | ||||
|                         fillData: o, | ||||
|                         source: ERC20BridgeSource.Native, | ||||
|                         gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|                     }; | ||||
|                 }), | ||||
|             ]; | ||||
| @@ -359,10 +366,8 @@ export class SwapQuoter { | ||||
|         const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts; | ||||
|         const calcOpts: GetMarketOrdersOpts = { | ||||
|             ...cloneOpts, | ||||
|             feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) => | ||||
|                 gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)), | ||||
|             ), | ||||
|             exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)), | ||||
|             gasPrice, | ||||
|         }; | ||||
|         // pass the QuoteRequestor on if rfqt enabled | ||||
|         if (calcOpts.rfqt !== undefined) { | ||||
| @@ -391,7 +396,6 @@ export class SwapQuoter { | ||||
|             marketOperation, | ||||
|             assetFillAmount, | ||||
|             gasPrice, | ||||
|             opts.gasSchedule, | ||||
|             opts.bridgeSlippage, | ||||
|         ); | ||||
|  | ||||
| @@ -494,7 +498,6 @@ function createSwapQuote( | ||||
|     operation: MarketOperation, | ||||
|     assetFillAmount: BigNumber, | ||||
|     gasPrice: BigNumber, | ||||
|     gasSchedule: FeeSchedule, | ||||
|     slippage: number, | ||||
| ): SwapQuote { | ||||
|     const { | ||||
| @@ -509,8 +512,8 @@ function createSwapQuote( | ||||
|  | ||||
|     // Calculate quote info | ||||
|     const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop | ||||
|         ? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage) | ||||
|         : calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage); | ||||
|         ? calculateTwoHopQuoteInfo(optimizedOrders, operation, slippage) | ||||
|         : calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, slippage); | ||||
|  | ||||
|     // Put together the swap quote | ||||
|     const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity; | ||||
| @@ -551,7 +554,6 @@ function calculateQuoteInfo( | ||||
|     operation: MarketOperation, | ||||
|     assetFillAmount: BigNumber, | ||||
|     gasPrice: BigNumber, | ||||
|     gasSchedule: FeeSchedule, | ||||
|     slippage: number, | ||||
| ): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } { | ||||
|     const bestCaseFillResult = simulateBestCaseFill({ | ||||
| @@ -559,7 +561,7 @@ function calculateQuoteInfo( | ||||
|         orders: optimizedOrders, | ||||
|         side: operation, | ||||
|         fillAmount: assetFillAmount, | ||||
|         opts: { gasSchedule }, | ||||
|         opts: {}, | ||||
|     }); | ||||
|  | ||||
|     const worstCaseFillResult = simulateWorstCaseFill({ | ||||
| @@ -567,7 +569,7 @@ function calculateQuoteInfo( | ||||
|         orders: optimizedOrders, | ||||
|         side: operation, | ||||
|         fillAmount: assetFillAmount, | ||||
|         opts: { gasSchedule, slippage }, | ||||
|         opts: { slippage }, | ||||
|     }); | ||||
|  | ||||
|     return { | ||||
| @@ -580,18 +582,12 @@ function calculateQuoteInfo( | ||||
| function calculateTwoHopQuoteInfo( | ||||
|     optimizedOrders: OptimizedMarketOrder[], | ||||
|     operation: MarketOperation, | ||||
|     gasSchedule: FeeSchedule, | ||||
|     slippage: number, | ||||
| ): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } { | ||||
|     const [firstHopOrder, secondHopOrder] = optimizedOrders; | ||||
|     const [firstHopFill] = firstHopOrder.fills; | ||||
|     const [secondHopFill] = secondHopOrder.fills; | ||||
|     const gas = new BigNumber( | ||||
|         gasSchedule[ERC20BridgeSource.MultiHop]!({ | ||||
|             firstHopSource: _.pick(firstHopFill, 'source', 'fillData'), | ||||
|             secondHopSource: _.pick(secondHopFill, 'source', 'fillData'), | ||||
|         }), | ||||
|     ).toNumber(); | ||||
|     const gas = (firstHopOrder.gasUsed || ZERO_AMOUNT).plus(secondHopFill.gasUsed || ZERO_AMOUNT).toNumber(); | ||||
|     return { | ||||
|         bestCaseQuoteInfo: { | ||||
|             makerAmount: operation === MarketOperation.Sell ? secondHopFill.output : secondHopFill.input, | ||||
|   | ||||
| @@ -256,7 +256,6 @@ export interface RfqRequestOpts { | ||||
|  * gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount | ||||
|  */ | ||||
| export interface SwapQuoteRequestOpts extends GetMarketOrdersOpts { | ||||
|     gasPrice?: BigNumber; | ||||
|     rfqt?: RfqRequestOpts; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,19 +1,11 @@ | ||||
| import { Web3Wrapper } from '@0x/dev-utils'; | ||||
| import { FillQuoteTransformerOrderType } from '@0x/protocol-utils'; | ||||
| import { BigNumber, logUtils } from '@0x/utils'; | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { MarketOperation } from '../../types'; | ||||
|  | ||||
| import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants'; | ||||
| import { | ||||
|     ComparisonPrice, | ||||
|     ERC20BridgeSource, | ||||
|     ExchangeProxyOverhead, | ||||
|     FeeEstimate, | ||||
|     FeeSchedule, | ||||
|     MarketSideLiquidity, | ||||
| } from './types'; | ||||
| import { COMPARISON_PRICE_DECIMALS, NATIVE_RFQT_GAS_USED, SOURCE_FLAGS } from './constants'; | ||||
| import { ComparisonPrice, ExchangeProxyOverhead, MarketSideLiquidity } from './types'; | ||||
|  | ||||
| /** | ||||
|  * Takes in an optimizer response and returns a price for RFQT MMs to beat | ||||
| @@ -22,42 +14,21 @@ import { | ||||
|  * @param adjustedRate the adjusted rate (accounting for fees) from the optimizer, maker/taker | ||||
|  * @param amount the amount specified by the client | ||||
|  * @param marketSideLiquidity the results from querying liquidity sources | ||||
|  * @param feeSchedule the fee schedule passed to the Optimizer | ||||
|  * @param gasPrice the gas price used to calculate costs | ||||
|  * @return ComparisonPrice object with the prices for RFQ MMs to beat | ||||
|  */ | ||||
| export function getComparisonPrices( | ||||
|     adjustedRate: BigNumber, | ||||
|     amount: BigNumber, | ||||
|     marketSideLiquidity: MarketSideLiquidity, | ||||
|     feeSchedule: FeeSchedule, | ||||
|     gasPrice: BigNumber, | ||||
|     exchangeProxyOverhead: ExchangeProxyOverhead, | ||||
| ): ComparisonPrice { | ||||
|     let wholeOrder: BigNumber | undefined; | ||||
|     let feeInEth: BigNumber | number; | ||||
|  | ||||
|     // HACK: get the fee penalty of a single 0x native order | ||||
|     // The FeeSchedule function takes in a `FillData` object and returns a fee estimate in ETH | ||||
|     // We don't have fill data here, we just want the cost of a single native order, so we pass in undefined | ||||
|     // This works because the feeSchedule returns a constant for Native orders, this will need | ||||
|     // to be tweaked if the feeSchedule for native orders uses the fillData passed in | ||||
|     // 2 potential issues: there is no native fee schedule or the fee schedule depends on fill data | ||||
|     if (feeSchedule[ERC20BridgeSource.Native] === undefined) { | ||||
|         logUtils.warn('ComparisonPrice function did not find native order fee schedule'); | ||||
|  | ||||
|         return { wholeOrder }; | ||||
|     } else { | ||||
|         try { | ||||
|             const fillFeeInEth = new BigNumber( | ||||
|                 (feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }), | ||||
|             ); | ||||
|             const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder)); | ||||
|             feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth); | ||||
|         } catch { | ||||
|             logUtils.warn('Native order fee schedule requires fill data'); | ||||
|  | ||||
|             return { wholeOrder }; | ||||
|         } | ||||
|     } | ||||
|     // Assuming EP overhead has been gas price adjusted | ||||
|     feeInEth = NATIVE_RFQT_GAS_USED.times(gasPrice).plus(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder)); | ||||
|  | ||||
|     // Calc native order fee penalty in output unit (maker units for sells, taker unit for buys) | ||||
|     const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero() | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses'; | ||||
| import { FillQuoteTransformerOrderType } from '@0x/protocol-utils'; | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import { formatBytes32String } from '@ethersproject/strings'; | ||||
|  | ||||
| @@ -7,25 +6,15 @@ import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder'; | ||||
|  | ||||
| import { SourceFilters } from './source_filters'; | ||||
| import { | ||||
|     BancorFillData, | ||||
|     CurveFillData, | ||||
|     CurveFunctionSelectors, | ||||
|     CurveInfo, | ||||
|     DODOFillData, | ||||
|     ERC20BridgeSource, | ||||
|     FeeSchedule, | ||||
|     FillData, | ||||
|     GetMarketOrdersOpts, | ||||
|     KyberSamplerOpts, | ||||
|     LidoInfo, | ||||
|     LiquidityProviderFillData, | ||||
|     LiquidityProviderRegistry, | ||||
|     MakerPsmFillData, | ||||
|     MultiHopFillData, | ||||
|     PsmInfo, | ||||
|     TokenAdjacencyGraph, | ||||
|     UniswapV2FillData, | ||||
|     UniswapV3FillData, | ||||
| } from './types'; | ||||
|  | ||||
| // tslint:disable: custom-no-magic-numbers no-bitwise | ||||
| @@ -41,7 +30,7 @@ export const ONE_HOUR_IN_SECONDS = 60 * 60; | ||||
| export const ONE_SECOND_MS = 1000; | ||||
| export const NULL_BYTES = '0x'; | ||||
| export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; | ||||
| export const SAMPLER_ADDRESS = '0x5555555555555555555555555555555555555555'; | ||||
| export const SAMPLER_ADDRESS = '0x5555555555555555555555555555555555555556'; | ||||
| export const COMPARISON_PRICE_DECIMALS = 10; | ||||
|  | ||||
| // TODO(kimpers): Consolidate this implementation with the one in @0x/token-metadata | ||||
| @@ -577,8 +566,6 @@ const CURVE_POLYGON_ATRICRYPTO_TOKENS = [POLYGON_TOKENS.amDAI, POLYGON_TOKENS.am | ||||
|  | ||||
| const createCurveExchangePool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: info.tokens, | ||||
|     metaTokens: undefined, | ||||
|     poolAddress: info.pool, | ||||
| @@ -587,8 +574,6 @@ const createCurveExchangePool = (info: { tokens: string[]; pool: string; gasSche | ||||
|  | ||||
| const createCurveExchangeUnderlyingPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: info.tokens, | ||||
|     metaTokens: undefined, | ||||
|     poolAddress: info.pool, | ||||
| @@ -597,8 +582,6 @@ const createCurveExchangeUnderlyingPool = (info: { tokens: string[]; pool: strin | ||||
|  | ||||
| const createCurveMetaTriPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: [...info.tokens, ...CURVE_TRI_POOL_MAINNET_TOKENS], | ||||
|     metaTokens: info.tokens, | ||||
|     poolAddress: info.pool, | ||||
| @@ -607,8 +590,6 @@ const createCurveMetaTriPool = (info: { tokens: string[]; pool: string; gasSched | ||||
|  | ||||
| const createCurveMetaTriBtcPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: [...info.tokens, ...CURVE_TRI_BTC_POOL_TOKEN], | ||||
|     metaTokens: info.tokens, | ||||
|     poolAddress: info.pool, | ||||
| @@ -617,8 +598,6 @@ const createCurveMetaTriBtcPool = (info: { tokens: string[]; pool: string; gasSc | ||||
|  | ||||
| const createCurveExchangeV2Pool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange_v2, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_v2, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: info.tokens, | ||||
|     metaTokens: undefined, | ||||
|     poolAddress: info.pool, | ||||
| @@ -627,8 +606,6 @@ const createCurveExchangeV2Pool = (info: { tokens: string[]; pool: string; gasSc | ||||
|  | ||||
| const createCurveV2MetaTriPool = (info: { tokens: string[]; pool: string; gasSchedule: number }) => ({ | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying_v2, | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying_v2, | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|     tokens: [...CURVE_POLYGON_ATRICRYPTO_UNDERLYING_TOKENS, ...info.tokens], | ||||
|     metaTokens: info.tokens, | ||||
|     poolAddress: info.pool, | ||||
| @@ -910,8 +887,6 @@ export const XSIGMA_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [SADDLE_POOLS.stables]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: SADDLE_POOLS.stables, | ||||
|         tokens: [MAINNET_TOKENS.DAI, MAINNET_TOKENS.USDC, MAINNET_TOKENS.USDT], | ||||
|         metaTokens: undefined, | ||||
| @@ -919,8 +894,6 @@ export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
|     }, | ||||
|     [SADDLE_POOLS.bitcoins]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: SADDLE_POOLS.bitcoins, | ||||
|         tokens: [MAINNET_TOKENS.tBTC, MAINNET_TOKENS.WBTC, MAINNET_TOKENS.RenBTC, MAINNET_TOKENS.sBTC], | ||||
|         metaTokens: undefined, | ||||
| @@ -931,8 +904,6 @@ export const SADDLE_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [SMOOTHY_POOLS.syUSD]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: SMOOTHY_POOLS.syUSD, | ||||
|         tokens: [ | ||||
|             MAINNET_TOKENS.USDT, | ||||
| @@ -952,8 +923,6 @@ export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [SMOOTHY_POOLS.syUSD]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: SMOOTHY_POOLS.syUSD, | ||||
|         tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC, BSC_TOKENS.DAI, BSC_TOKENS.PAX, BSC_TOKENS.UST], | ||||
|         metaTokens: undefined, | ||||
| @@ -964,8 +933,6 @@ export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [NERVE_POOLS.threePool]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: NERVE_POOLS.threePool, | ||||
|         tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC], | ||||
|         metaTokens: undefined, | ||||
| @@ -976,8 +943,6 @@ export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const FIREBIRDONESWAP_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [FIREBIRDONESWAP_BSC_POOLS.oneswap]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: FIREBIRDONESWAP_BSC_POOLS.oneswap, | ||||
|         tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.DAI, BSC_TOKENS.USDC], | ||||
|         metaTokens: undefined, | ||||
| @@ -988,8 +953,6 @@ export const FIREBIRDONESWAP_BSC_INFOS: { [name: string]: CurveInfo } = { | ||||
| export const FIREBIRDONESWAP_POLYGON_INFOS: { [name: string]: CurveInfo } = { | ||||
|     [FIREBIRDONESWAP_POLYGON_POOLS.oneswap]: { | ||||
|         exchangeFunctionSelector: CurveFunctionSelectors.swap, | ||||
|         sellQuoteFunctionSelector: CurveFunctionSelectors.calculateSwap, | ||||
|         buyQuoteFunctionSelector: CurveFunctionSelectors.None, | ||||
|         poolAddress: FIREBIRDONESWAP_POLYGON_POOLS.oneswap, | ||||
|         tokens: [POLYGON_TOKENS.DAI, POLYGON_TOKENS.USDC, POLYGON_TOKENS.USDT], | ||||
|         metaTokens: undefined, | ||||
| @@ -1141,11 +1104,7 @@ export const KYBER_DMM_ROUTER_BY_CHAIN_ID = valueByChainId<string>( | ||||
|  | ||||
| export const MOONISWAP_REGISTRIES_BY_CHAIN_ID = valueByChainId( | ||||
|     { | ||||
|         [ChainId.Mainnet]: [ | ||||
|             '0x71CD6666064C3A1354a3B4dca5fA1E2D3ee7D303', | ||||
|             '0xc4a8b7e29e3c8ec560cd4945c1cf3461a85a148d', | ||||
|             '0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643', | ||||
|         ], | ||||
|         [ChainId.Mainnet]: ['0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643'], | ||||
|         [ChainId.BSC]: ['0xd41b24bba51fac0e4827b6f94c0d6ddeb183cd64'], | ||||
|     }, | ||||
|     [] as string[], | ||||
| @@ -1411,132 +1370,8 @@ export const POLYDEX_ROUTER_BY_CHAIN_ID = valueByChainId<string>( | ||||
|     NULL_ADDRESS, | ||||
| ); | ||||
|  | ||||
| const uniswapV2CloneGasSchedule = (fillData?: FillData) => { | ||||
|     // TODO: Different base cost if to/from ETH. | ||||
|     let gas = 90e3; | ||||
|     const path = (fillData as UniswapV2FillData).tokenAddressPath; | ||||
|     if (path.length > 2) { | ||||
|         gas += (path.length - 2) * 60e3; // +60k for each hop. | ||||
|     } | ||||
|     return gas; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Calculated gross gas cost of the underlying exchange. | ||||
|  * The cost of switching from one source to another, assuming | ||||
|  * we are in the middle of a transaction. | ||||
|  * I.e remove the overhead cost of ExchangeProxy (130k) and | ||||
|  * the ethereum transaction cost (21k) | ||||
|  */ | ||||
| // tslint:disable:custom-no-magic-numbers | ||||
| export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = { | ||||
|     [ERC20BridgeSource.Native]: fillData => { | ||||
|         // TODO jacob re-order imports so there is no circular rependency with SignedNativeOrder | ||||
|         const nativeFillData = fillData as { type: FillQuoteTransformerOrderType }; | ||||
|         return nativeFillData && nativeFillData.type === FillQuoteTransformerOrderType.Limit | ||||
|             ? PROTOCOL_FEE_MULTIPLIER.plus(100e3).toNumber() | ||||
|             : // TODO jacob revisit wth v4 LimitOrders | ||||
|               100e3; | ||||
|     }, | ||||
|     [ERC20BridgeSource.Uniswap]: () => 90e3, | ||||
|     [ERC20BridgeSource.LiquidityProvider]: fillData => { | ||||
|         return (fillData as LiquidityProviderFillData).gasCost || 100e3; | ||||
|     }, | ||||
|     [ERC20BridgeSource.Eth2Dai]: () => 400e3, | ||||
|     [ERC20BridgeSource.Kyber]: () => 450e3, | ||||
|     [ERC20BridgeSource.Curve]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.CurveV2]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Swerve]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.SnowSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Nerve]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Smoothy]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.Saddle]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.XSigma]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.FirebirdOneSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule, | ||||
|     [ERC20BridgeSource.MultiBridge]: () => 350e3, | ||||
|     [ERC20BridgeSource.UniswapV2]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.SushiSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.CryptoCom]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.Linkswap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.Balancer]: () => 120e3, | ||||
|     [ERC20BridgeSource.BalancerV2]: () => 100e3, | ||||
|     [ERC20BridgeSource.Cream]: () => 120e3, | ||||
|     [ERC20BridgeSource.MStable]: () => 200e3, | ||||
|     [ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => { | ||||
|         const psmFillData = fillData as MakerPsmFillData; | ||||
|         return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3; | ||||
|     }, | ||||
|     [ERC20BridgeSource.Mooniswap]: () => 130e3, | ||||
|     [ERC20BridgeSource.Shell]: () => 170e3, | ||||
|     [ERC20BridgeSource.Component]: () => 188e3, | ||||
|     [ERC20BridgeSource.MultiHop]: (fillData?: FillData) => { | ||||
|         const firstHop = (fillData as MultiHopFillData).firstHopSource; | ||||
|         const secondHop = (fillData as MultiHopFillData).secondHopSource; | ||||
|         const firstHopGas = DEFAULT_GAS_SCHEDULE[firstHop.source](firstHop.fillData); | ||||
|         const secondHopGas = DEFAULT_GAS_SCHEDULE[secondHop.source](secondHop.fillData); | ||||
|         return new BigNumber(firstHopGas) | ||||
|             .plus(secondHopGas) | ||||
|             .plus(30e3) | ||||
|             .toNumber(); | ||||
|     }, | ||||
|     [ERC20BridgeSource.Dodo]: (fillData?: FillData) => { | ||||
|         const isSellBase = (fillData as DODOFillData).isSellBase; | ||||
|         // Sell base is cheaper as it is natively supported | ||||
|         // sell quote requires additional calculation and overhead | ||||
|         return isSellBase ? 180e3 : 300e3; | ||||
|     }, | ||||
|     [ERC20BridgeSource.DodoV2]: (_fillData?: FillData) => 100e3, | ||||
|     [ERC20BridgeSource.Bancor]: (fillData?: FillData) => { | ||||
|         let gas = 200e3; | ||||
|         const path = (fillData as BancorFillData).path; | ||||
|         if (path.length > 2) { | ||||
|             gas += (path.length - 2) * 60e3; // +60k for each hop. | ||||
|         } | ||||
|         return gas; | ||||
|     }, | ||||
|     [ERC20BridgeSource.KyberDmm]: (fillData?: FillData) => { | ||||
|         // TODO: Different base cost if to/from ETH. | ||||
|         let gas = 95e3; | ||||
|         const path = (fillData as UniswapV2FillData).tokenAddressPath; | ||||
|         if (path.length > 2) { | ||||
|             gas += (path.length - 2) * 65e3; // +65k for each hop. | ||||
|         } | ||||
|         return gas; | ||||
|     }, | ||||
|     [ERC20BridgeSource.UniswapV3]: (fillData?: FillData) => { | ||||
|         let gas = 100e3; | ||||
|         const path = (fillData as UniswapV3FillData).tokenAddressPath; | ||||
|         if (path.length > 2) { | ||||
|             gas += (path.length - 2) * 32e3; // +32k for each hop. | ||||
|         } | ||||
|         return gas; | ||||
|     }, | ||||
|     [ERC20BridgeSource.Lido]: () => 226e3, | ||||
|  | ||||
|     // | ||||
|     // BSC | ||||
|     // | ||||
|     [ERC20BridgeSource.PancakeSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.PancakeSwapV2]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.BakerySwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.ApeSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.CafeSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.CheeseSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.JulSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.WaultSwap]: uniswapV2CloneGasSchedule, | ||||
|  | ||||
|     // | ||||
|     // Polygon | ||||
|     // | ||||
|     [ERC20BridgeSource.QuickSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.ComethSwap]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule, | ||||
|     [ERC20BridgeSource.Polydex]: uniswapV2CloneGasSchedule, | ||||
| }; | ||||
|  | ||||
| export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE }; | ||||
| export const NATIVE_RFQT_GAS_USED = new BigNumber(100e3); | ||||
| export const NATIVE_LIMIT_ORDER_GAS_USED = NATIVE_RFQT_GAS_USED.plus(PROTOCOL_FEE_MULTIPLIER); | ||||
|  | ||||
| export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000); | ||||
|  | ||||
| @@ -1552,11 +1387,10 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { | ||||
|     maxFallbackSlippage: 0.05, | ||||
|     numSamples: 13, | ||||
|     sampleDistributionBase: 1.05, | ||||
|     feeSchedule: DEFAULT_FEE_SCHEDULE, | ||||
|     gasSchedule: DEFAULT_GAS_SCHEDULE, | ||||
|     exchangeProxyOverhead: () => ZERO_AMOUNT, | ||||
|     allowFallback: true, | ||||
|     shouldGenerateQuoteReport: true, | ||||
|     shouldIncludePriceComparisonsReport: false, | ||||
|     tokenAdjacencyGraph: { default: [] }, | ||||
|     gasPrice: new BigNumber(1e9), | ||||
| }; | ||||
|   | ||||
| @@ -3,8 +3,14 @@ import { BigNumber, hexUtils } from '@0x/utils'; | ||||
|  | ||||
| import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types'; | ||||
|  | ||||
| import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants'; | ||||
| import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types'; | ||||
| import { | ||||
|     NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|     NATIVE_RFQT_GAS_USED, | ||||
|     POSITIVE_INF, | ||||
|     SOURCE_FLAGS, | ||||
|     ZERO_AMOUNT, | ||||
| } from './constants'; | ||||
| import { DexSample, ERC20BridgeSource, Fill } from './types'; | ||||
|  | ||||
| // tslint:disable: prefer-for-of no-bitwise completed-docs | ||||
|  | ||||
| @@ -13,17 +19,16 @@ import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types'; | ||||
|  */ | ||||
| export function createFills(opts: { | ||||
|     side: MarketOperation; | ||||
|     gasPrice: BigNumber; | ||||
|     orders?: NativeOrderWithFillableAmounts[]; | ||||
|     dexQuotes?: DexSample[][]; | ||||
|     targetInput?: BigNumber; | ||||
|     outputAmountPerEth?: BigNumber; | ||||
|     inputAmountPerEth?: BigNumber; | ||||
|     excludedSources?: ERC20BridgeSource[]; | ||||
|     feeSchedule?: FeeSchedule; | ||||
| }): Fill[][] { | ||||
|     const { side } = opts; | ||||
|     const excludedSources = opts.excludedSources || []; | ||||
|     const feeSchedule = opts.feeSchedule || {}; | ||||
|     const orders = opts.orders || []; | ||||
|     const dexQuotes = opts.dexQuotes || []; | ||||
|     const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT; | ||||
| @@ -35,11 +40,11 @@ export function createFills(opts: { | ||||
|         opts.targetInput, | ||||
|         outputAmountPerEth, | ||||
|         inputAmountPerEth, | ||||
|         feeSchedule, | ||||
|         opts.gasPrice, | ||||
|     ); | ||||
|     // Create DEX fills. | ||||
|     const dexFills = dexQuotes.map(singleSourceSamples => | ||||
|         dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule), | ||||
|         dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, opts.gasPrice), | ||||
|     ); | ||||
|     return [...dexFills, nativeFills] | ||||
|         .map(p => clipFillsToInput(p, opts.targetInput)) | ||||
| @@ -77,7 +82,7 @@ function nativeOrdersToFills( | ||||
|     targetInput: BigNumber = POSITIVE_INF, | ||||
|     outputAmountPerEth: BigNumber, | ||||
|     inputAmountPerEth: BigNumber, | ||||
|     fees: FeeSchedule, | ||||
|     gasPrice: BigNumber, | ||||
| ): Fill[] { | ||||
|     const sourcePathId = hexUtils.random(); | ||||
|     // Create a single path from all orders. | ||||
| @@ -88,10 +93,12 @@ function nativeOrdersToFills( | ||||
|         const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount); | ||||
|         const input = side === MarketOperation.Sell ? takerAmount : makerAmount; | ||||
|         const output = side === MarketOperation.Sell ? makerAmount : takerAmount; | ||||
|         const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o); | ||||
|         const gasUsed = | ||||
|             o.type === FillQuoteTransformerOrderType.Limit ? NATIVE_LIMIT_ORDER_GAS_USED : NATIVE_RFQT_GAS_USED; | ||||
|         const feeInEth = gasUsed.times(gasPrice); | ||||
|         const outputPenalty = !outputAmountPerEth.isZero() | ||||
|             ? outputAmountPerEth.times(fee) | ||||
|             : inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input)); | ||||
|             ? outputAmountPerEth.times(feeInEth) | ||||
|             : inputAmountPerEth.times(feeInEth).times(output.dividedToIntegerBy(input)); | ||||
|         // targetInput can be less than the order size | ||||
|         // whilst the penalty is constant, it affects the adjusted output | ||||
|         // only up until the target has been exhausted. | ||||
| @@ -104,6 +111,7 @@ function nativeOrdersToFills( | ||||
|             side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty); | ||||
|         const adjustedRate = | ||||
|             side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput); | ||||
|  | ||||
|         // Skip orders with rates that are <= 0. | ||||
|         if (adjustedRate.lte(0)) { | ||||
|             continue; | ||||
| @@ -119,6 +127,7 @@ function nativeOrdersToFills( | ||||
|             parent: undefined, // TBD | ||||
|             source: ERC20BridgeSource.Native, | ||||
|             type, | ||||
|             gasUsed, | ||||
|             fillData: { ...o }, | ||||
|         }); | ||||
|     } | ||||
| @@ -137,7 +146,7 @@ function dexSamplesToFills( | ||||
|     samples: DexSample[], | ||||
|     outputAmountPerEth: BigNumber, | ||||
|     inputAmountPerEth: BigNumber, | ||||
|     fees: FeeSchedule, | ||||
|     gasPrice: BigNumber, | ||||
| ): Fill[] { | ||||
|     const sourcePathId = hexUtils.random(); | ||||
|     const fills: Fill[] = []; | ||||
| @@ -152,10 +161,15 @@ function dexSamplesToFills( | ||||
|         const { source, fillData } = sample; | ||||
|         const input = sample.input.minus(prevSample ? prevSample.input : 0); | ||||
|         const output = sample.output.minus(prevSample ? prevSample.output : 0); | ||||
|         const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0; | ||||
|  | ||||
|         if (!sample.gasUsed || sample.gasUsed.isZero()) { | ||||
|             throw new Error(`${sample.source} gas used missing or 0`); | ||||
|         } | ||||
|  | ||||
|         let penalty = ZERO_AMOUNT; | ||||
|         if (i === 0) { | ||||
|             // Only the first fill in a DEX path incurs a penalty. | ||||
|             const fee = gasPrice.times(sample.gasUsed); | ||||
|             penalty = !outputAmountPerEth.isZero() | ||||
|                 ? outputAmountPerEth.times(fee) | ||||
|                 : inputAmountPerEth.times(fee).times(output.dividedToIntegerBy(input)); | ||||
| @@ -173,6 +187,7 @@ function dexSamplesToFills( | ||||
|             index: i, | ||||
|             parent: i !== 0 ? fills[fills.length - 1] : undefined, | ||||
|             flags: SOURCE_FLAGS[source], | ||||
|             gasUsed: sample.gasUsed, | ||||
|         }); | ||||
|     } | ||||
|     return fills; | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| import { FillQuoteTransformerOrderType, RfqOrder } from '@0x/protocol-utils'; | ||||
| import { BigNumber, NULL_ADDRESS } from '@0x/utils'; | ||||
| import * as ethjs from 'ethereumjs-util'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { artifacts } from '../..'; | ||||
| import { DEFAULT_INFO_LOGGER, INVALID_SIGNATURE } from '../../constants'; | ||||
| import { | ||||
|     AssetSwapperContractAddresses, | ||||
| @@ -36,7 +38,7 @@ import { | ||||
|     ZERO_AMOUNT, | ||||
| } from './constants'; | ||||
| import { createFills } from './fills'; | ||||
| import { getBestTwoHopQuote } from './multihop_utils'; | ||||
| import { getBestTwoHopQuote, getIntermediateTokens } from './multihop_utils'; | ||||
| import { createOrdersFromTwoHopSample } from './orders'; | ||||
| import { PathPenaltyOpts } from './path'; | ||||
| import { fillsToSortedPaths, findOptimalPathAsync } from './path_optimizer'; | ||||
| @@ -50,11 +52,21 @@ import { | ||||
|     GenerateOptimizedOrdersOpts, | ||||
|     GetMarketOrdersOpts, | ||||
|     MarketSideLiquidity, | ||||
|     MultiHopFillData, | ||||
|     OptimizerResult, | ||||
|     OptimizerResultWithReport, | ||||
|     OrderDomain, | ||||
| } from './types'; | ||||
|  | ||||
| const HACKED_ERC20_BYTECODE = _.get(artifacts.HackedERC20, 'compilerOutput.evm.deployedBytecode.object'); | ||||
| const GAS_OVERHEAD_BYTECODE = _.get(artifacts.GasOverhead, 'compilerOutput.evm.deployedBytecode.object'); | ||||
| const DELEGEATE_HACKED_ERC20_BYTECODE = _.get( | ||||
|     artifacts.DelegateHackedERC20, | ||||
|     'compilerOutput.evm.deployedBytecode.object', | ||||
| ); | ||||
| const HACKED_ERC20_ADDRESS = '0xDEf1000000000000000000000000000000DE7d37'; | ||||
| const GAS_OVERHEAD_ADDRESS = '0xDeF1000000000000000000000000000000001337'; | ||||
|  | ||||
| // tslint:disable:boolean-naming | ||||
|  | ||||
| export class MarketOperationUtils { | ||||
| @@ -63,6 +75,7 @@ export class MarketOperationUtils { | ||||
|     private readonly _feeSources: SourceFilters; | ||||
|     private readonly _nativeFeeToken: string; | ||||
|     private readonly _nativeFeeTokenAmount: BigNumber; | ||||
|     private readonly _contractCodeByAddress: { [address: string]: string | undefined } = {}; | ||||
|  | ||||
|     private static _computeQuoteReport( | ||||
|         quoteRequestor: QuoteRequestor | undefined, | ||||
| @@ -131,34 +144,48 @@ export class MarketOperationUtils { | ||||
|         // Used to determine whether the tx origin is an EOA or a contract | ||||
|         const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS; | ||||
|  | ||||
|         const { overrides } = await this._fetchTokenOverridesAsync(takerToken, makerToken); | ||||
|         // Call the sampler contract. | ||||
|         const samplerPromise = this._sampler.executeAsync( | ||||
|             this._sampler.getTokenDecimals([makerToken, takerToken]), | ||||
|             // Get native order fillable amounts. | ||||
|             this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), | ||||
|             // Get ETH -> maker token price. | ||||
|             this._sampler.getMedianSellRate( | ||||
|                 feeSourceFilters.sources, | ||||
|                 makerToken, | ||||
|                 this._nativeFeeToken, | ||||
|                 this._nativeFeeTokenAmount, | ||||
|             ), | ||||
|             // Get ETH -> taker token price. | ||||
|             this._sampler.getMedianSellRate( | ||||
|                 feeSourceFilters.sources, | ||||
|                 takerToken, | ||||
|                 this._nativeFeeToken, | ||||
|                 this._nativeFeeTokenAmount, | ||||
|             ), | ||||
|             // Get sell quotes for taker -> maker. | ||||
|             this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), | ||||
|             this._sampler.getTwoHopSellQuotes( | ||||
|                 quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], | ||||
|                 makerToken, | ||||
|                 takerToken, | ||||
|                 takerAmount, | ||||
|             ), | ||||
|             this._sampler.isAddressContract(txOrigin), | ||||
|         const samplerPromise = this._sampler.executeBatchAsync( | ||||
|             [ | ||||
|                 this._sampler.getTokenDecimals([makerToken, takerToken]), | ||||
|                 // Get native order fillable amounts. | ||||
|                 this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), | ||||
|                 // Get ETH -> maker token price. | ||||
|                 this._sampler.getMedianSellRate( | ||||
|                     feeSourceFilters.sources, | ||||
|                     makerToken, | ||||
|                     this._nativeFeeToken, | ||||
|                     this._nativeFeeTokenAmount, | ||||
|                 ), | ||||
|                 // Get ETH -> taker token price. | ||||
|                 this._sampler.getMedianSellRate( | ||||
|                     feeSourceFilters.sources, | ||||
|                     takerToken, | ||||
|                     this._nativeFeeToken, | ||||
|                     this._nativeFeeTokenAmount, | ||||
|                 ), | ||||
|                 // Get sell quotes for taker -> maker. | ||||
|                 this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), | ||||
|                 this._sampler.isAddressContract(txOrigin), | ||||
|             ], | ||||
|             { | ||||
|                 overrides, | ||||
|             }, | ||||
|         ); | ||||
|         // Perform the MultiHop sell quotes in a separate request | ||||
|         const multiHopsamplerPromise = this._sampler.executeBatchAsync( | ||||
|             [ | ||||
|                 this._sampler.getTwoHopSellQuotes( | ||||
|                     quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], | ||||
|                     makerToken, | ||||
|                     takerToken, | ||||
|                     takerAmount, | ||||
|                 ), | ||||
|             ], | ||||
|             { | ||||
|                 overrides, | ||||
|             }, | ||||
|         ); | ||||
|  | ||||
|         // Refresh the cached pools asynchronously if required | ||||
| @@ -171,14 +198,15 @@ export class MarketOperationUtils { | ||||
|                 outputAmountPerEth, | ||||
|                 inputAmountPerEth, | ||||
|                 dexQuotes, | ||||
|                 rawTwoHopQuotes, | ||||
|                 isTxOriginContract, | ||||
|             ], | ||||
|         ] = await Promise.all([samplerPromise]); | ||||
|             [rawTwoHopQuotes], | ||||
|         ] = await Promise.all([samplerPromise, multiHopsamplerPromise]); | ||||
|  | ||||
|         // Filter out any invalid two hop quotes where we couldn't find a route | ||||
|         const twoHopQuotes = rawTwoHopQuotes.filter( | ||||
|             q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, | ||||
|             (q: DexSample<MultiHopFillData>) => | ||||
|                 q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, | ||||
|         ); | ||||
|  | ||||
|         const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals; | ||||
| @@ -232,35 +260,42 @@ export class MarketOperationUtils { | ||||
|         // Used to determine whether the tx origin is an EOA or a contract | ||||
|         const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS; | ||||
|  | ||||
|         const { overrides } = await this._fetchTokenOverridesAsync(takerToken, makerToken); | ||||
|         // Call the sampler contract. | ||||
|         const samplerPromise = this._sampler.executeAsync( | ||||
|             this._sampler.getTokenDecimals([makerToken, takerToken]), | ||||
|             // Get native order fillable amounts. | ||||
|             this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), | ||||
|             // Get ETH -> makerToken token price. | ||||
|             this._sampler.getMedianSellRate( | ||||
|                 feeSourceFilters.sources, | ||||
|                 makerToken, | ||||
|                 this._nativeFeeToken, | ||||
|                 this._nativeFeeTokenAmount, | ||||
|             ), | ||||
|             // Get ETH -> taker token price. | ||||
|             this._sampler.getMedianSellRate( | ||||
|                 feeSourceFilters.sources, | ||||
|                 takerToken, | ||||
|                 this._nativeFeeToken, | ||||
|                 this._nativeFeeTokenAmount, | ||||
|             ), | ||||
|             // Get buy quotes for taker -> maker. | ||||
|             this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), | ||||
|         const samplerPromise = this._sampler.executeBatchAsync( | ||||
|             [ | ||||
|                 this._sampler.getTokenDecimals([makerToken, takerToken]), | ||||
|                 // Get native order fillable amounts. | ||||
|                 this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy), | ||||
|                 // Get ETH -> makerToken token price. | ||||
|                 this._sampler.getMedianSellRate( | ||||
|                     feeSourceFilters.sources, | ||||
|                     makerToken, | ||||
|                     this._nativeFeeToken, | ||||
|                     this._nativeFeeTokenAmount, | ||||
|                 ), | ||||
|                 // Get ETH -> taker token price. | ||||
|                 this._sampler.getMedianSellRate( | ||||
|                     feeSourceFilters.sources, | ||||
|                     takerToken, | ||||
|                     this._nativeFeeToken, | ||||
|                     this._nativeFeeTokenAmount, | ||||
|                 ), | ||||
|                 // Get buy quotes for taker -> maker. | ||||
|                 this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts), | ||||
|                 this._sampler.isAddressContract(txOrigin), | ||||
|             ], | ||||
|             { overrides }, | ||||
|         ); | ||||
|  | ||||
|         const multiHopPromise = this._sampler.executeBatchAsync([ | ||||
|             this._sampler.getTwoHopBuyQuotes( | ||||
|                 quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [], | ||||
|                 makerToken, | ||||
|                 takerToken, | ||||
|                 makerAmount, | ||||
|             ), | ||||
|             this._sampler.isAddressContract(txOrigin), | ||||
|         ); | ||||
|         ]); | ||||
|  | ||||
|         // Refresh the cached pools asynchronously if required | ||||
|         void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken); | ||||
| @@ -272,14 +307,15 @@ export class MarketOperationUtils { | ||||
|                 ethToMakerAssetRate, | ||||
|                 ethToTakerAssetRate, | ||||
|                 dexQuotes, | ||||
|                 rawTwoHopQuotes, | ||||
|                 isTxOriginContract, | ||||
|             ], | ||||
|         ] = await Promise.all([samplerPromise]); | ||||
|             [rawTwoHopQuotes], | ||||
|         ] = await Promise.all([samplerPromise, multiHopPromise]); | ||||
|  | ||||
|         // Filter out any invalid two hop quotes where we couldn't find a route | ||||
|         const twoHopQuotes = rawTwoHopQuotes.filter( | ||||
|             q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, | ||||
|             (q: DexSample<MultiHopFillData>) => | ||||
|                 q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource, | ||||
|         ); | ||||
|  | ||||
|         const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals; | ||||
| @@ -324,17 +360,17 @@ export class MarketOperationUtils { | ||||
|     public async getBatchMarketBuyOrdersAsync( | ||||
|         batchNativeOrders: SignedNativeOrder[][], | ||||
|         makerAmounts: BigNumber[], | ||||
|         opts?: Partial<GetMarketOrdersOpts>, | ||||
|         gasPrice: BigNumber, | ||||
|         opts: GetMarketOrdersOpts, | ||||
|     ): Promise<Array<OptimizerResult | undefined>> { | ||||
|         if (batchNativeOrders.length === 0) { | ||||
|             throw new Error(AggregationError.EmptyOrders); | ||||
|         } | ||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||
|  | ||||
|         const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources); | ||||
|         const requestFilters = new SourceFilters().exclude(opts.excludedSources).include(opts.includedSources); | ||||
|         const quoteSourceFilters = this._buySources.merge(requestFilters); | ||||
|  | ||||
|         const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources); | ||||
|         const feeSourceFilters = this._feeSources.exclude(opts.excludedFeeSources); | ||||
|  | ||||
|         const ops = [ | ||||
|             ...batchNativeOrders.map(orders => | ||||
| @@ -402,11 +438,11 @@ export class MarketOperationUtils { | ||||
|                             isRfqSupported: false, | ||||
|                         }, | ||||
|                         { | ||||
|                             bridgeSlippage: _opts.bridgeSlippage, | ||||
|                             maxFallbackSlippage: _opts.maxFallbackSlippage, | ||||
|                             excludedSources: _opts.excludedSources, | ||||
|                             feeSchedule: _opts.feeSchedule, | ||||
|                             allowFallback: _opts.allowFallback, | ||||
|                             bridgeSlippage: opts.bridgeSlippage, | ||||
|                             maxFallbackSlippage: opts.maxFallbackSlippage, | ||||
|                             excludedSources: opts.excludedSources, | ||||
|                             allowFallback: opts.allowFallback, | ||||
|                             gasPrice, | ||||
|                         }, | ||||
|                     ); | ||||
|                     return optimizerResult; | ||||
| @@ -466,7 +502,7 @@ export class MarketOperationUtils { | ||||
|             outputAmountPerEth, | ||||
|             inputAmountPerEth, | ||||
|             excludedSources: opts.excludedSources, | ||||
|             feeSchedule: opts.feeSchedule, | ||||
|             gasPrice: opts.gasPrice, | ||||
|         }); | ||||
|  | ||||
|         // Find the optimal path. | ||||
| @@ -490,7 +526,7 @@ export class MarketOperationUtils { | ||||
|  | ||||
|         const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote( | ||||
|             marketSideLiquidity, | ||||
|             opts.feeSchedule, | ||||
|             opts.gasPrice, | ||||
|             opts.exchangeProxyOverhead, | ||||
|         ); | ||||
|         if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) { | ||||
| @@ -558,16 +594,15 @@ export class MarketOperationUtils { | ||||
|         nativeOrders: SignedNativeOrder[], | ||||
|         amount: BigNumber, | ||||
|         side: MarketOperation, | ||||
|         opts?: Partial<GetMarketOrdersOpts>, | ||||
|         opts: GetMarketOrdersOpts, | ||||
|     ): Promise<OptimizerResultWithReport> { | ||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||
|         const optimizerOpts: GenerateOptimizedOrdersOpts = { | ||||
|             bridgeSlippage: _opts.bridgeSlippage, | ||||
|             maxFallbackSlippage: _opts.maxFallbackSlippage, | ||||
|             excludedSources: _opts.excludedSources, | ||||
|             feeSchedule: _opts.feeSchedule, | ||||
|             allowFallback: _opts.allowFallback, | ||||
|             exchangeProxyOverhead: _opts.exchangeProxyOverhead, | ||||
|             bridgeSlippage: opts.bridgeSlippage, | ||||
|             maxFallbackSlippage: opts.maxFallbackSlippage, | ||||
|             excludedSources: opts.excludedSources, | ||||
|             allowFallback: opts.allowFallback, | ||||
|             exchangeProxyOverhead: opts.exchangeProxyOverhead, | ||||
|             gasPrice: opts.gasPrice, | ||||
|         }; | ||||
|  | ||||
|         if (nativeOrders.length === 0) { | ||||
| @@ -579,7 +614,7 @@ export class MarketOperationUtils { | ||||
|             side === MarketOperation.Sell | ||||
|                 ? this.getMarketSellLiquidityAsync.bind(this) | ||||
|                 : this.getMarketBuyLiquidityAsync.bind(this); | ||||
|         const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts); | ||||
|         const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, opts); | ||||
|         let optimizerResult: OptimizerResult | undefined; | ||||
|         try { | ||||
|             optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts); | ||||
| @@ -600,13 +635,13 @@ export class MarketOperationUtils { | ||||
|                 optimizerResult.adjustedRate, | ||||
|                 amount, | ||||
|                 marketSideLiquidity, | ||||
|                 _opts.feeSchedule, | ||||
|                 _opts.exchangeProxyOverhead, | ||||
|                 opts.gasPrice, | ||||
|                 opts.exchangeProxyOverhead, | ||||
|             ).wholeOrder; | ||||
|         } | ||||
|  | ||||
|         // If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result | ||||
|         const { rfqt } = _opts; | ||||
|         const { rfqt } = opts; | ||||
|         if ( | ||||
|             marketSideLiquidity.isRfqSupported && | ||||
|             rfqt && | ||||
| @@ -697,9 +732,9 @@ export class MarketOperationUtils { | ||||
|  | ||||
|         // Compute Quote Report and return the results. | ||||
|         let quoteReport: QuoteReport | undefined; | ||||
|         if (_opts.shouldGenerateQuoteReport) { | ||||
|         if (opts.shouldGenerateQuoteReport) { | ||||
|             quoteReport = MarketOperationUtils._computeQuoteReport( | ||||
|                 _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, | ||||
|                 opts.rfqt ? opts.rfqt.quoteRequestor : undefined, | ||||
|                 marketSideLiquidity, | ||||
|                 optimizerResult, | ||||
|                 wholeOrderPrice, | ||||
| @@ -707,9 +742,9 @@ export class MarketOperationUtils { | ||||
|         } | ||||
|  | ||||
|         let priceComparisonsReport: PriceComparisonsReport | undefined; | ||||
|         if (_opts.shouldIncludePriceComparisonsReport) { | ||||
|         if (opts.shouldIncludePriceComparisonsReport) { | ||||
|             priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport( | ||||
|                 _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined, | ||||
|                 opts.rfqt ? opts.rfqt.quoteRequestor : undefined, | ||||
|                 marketSideLiquidity, | ||||
|                 wholeOrderPrice, | ||||
|             ); | ||||
| @@ -727,6 +762,61 @@ export class MarketOperationUtils { | ||||
|             }), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     private async _fetchTokenOverridesAsync( | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|     ): Promise<{ overrides: { [address: string]: { code: string } } }> { | ||||
|         const overrides: { [address: string]: { code: string } } = {}; | ||||
|         // Set the gas overhead counter to a known address | ||||
|         overrides[GAS_OVERHEAD_ADDRESS] = { code: GAS_OVERHEAD_BYTECODE }; | ||||
|         // Set the fixed impl of a HackedERC20 bytecode, all other tokens use a delegate call | ||||
|         overrides[HACKED_ERC20_ADDRESS] = { code: HACKED_ERC20_BYTECODE }; | ||||
|  | ||||
|         if (!this._sampler.tokenAdjacencyGraph) { | ||||
|             return { overrides }; | ||||
|         } | ||||
|  | ||||
|         // Allow the tokens bytecode to be overwritten using geths override functionality | ||||
|         const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this._sampler.tokenAdjacencyGraph); | ||||
|         const tokens = [takerToken, makerToken, ...intermediateTokens].map(t => t.toLowerCase()); | ||||
|  | ||||
|         // Fetch all of the missing token codes | ||||
|         const missingTokenCodesTokens = tokens.filter(t => !this._contractCodeByAddress[t]); | ||||
|         if (missingTokenCodesTokens.length > 0) { | ||||
|             const missingTokenCodes = await this._sampler.executeBatchAsync( | ||||
|                 missingTokenCodesTokens.map(t => this._sampler.getCode(t)), | ||||
|             ); | ||||
|             missingTokenCodes.forEach((code, i) => { | ||||
|                 this._contractCodeByAddress[missingTokenCodesTokens[i]] = code; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         const tokenCodes = tokens.map(t => this._contractCodeByAddress[t]!); | ||||
|  | ||||
|         const nativeWrappedToken = NATIVE_FEE_TOKEN_BY_CHAIN_ID[this._sampler.chainId]; | ||||
|         tokens.forEach((token, i) => { | ||||
|             // Skip overriding WETH like token as this can be used directly with a deposit | ||||
|             if (token === nativeWrappedToken) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const tokenImplAddress = ethjs.bufferToHex( | ||||
|                 ethjs.setLengthLeft( | ||||
|                     // tslint:disable-next-line: custom-no-magic-numbers prefer-template | ||||
|                     ethjs.toBuffer(`0x${new BigNumber(token.toLowerCase()).plus(1).toString(16)}`), | ||||
|                     // tslint:disable-next-line: custom-no-magic-numbers prefer-template | ||||
|                     20, | ||||
|                 ), | ||||
|             ); | ||||
|             // Override the original implementation with the HackedERC20 delegate | ||||
|             overrides[token] = { code: DELEGEATE_HACKED_ERC20_BYTECODE }; | ||||
|             // Specify the original implementation at a new address (+1) | ||||
|             overrides[tokenImplAddress] = { code: tokenCodes[i] }; | ||||
|         }); | ||||
|  | ||||
|         return { overrides }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // tslint:disable: max-file-line-count | ||||
|   | ||||
| @@ -0,0 +1,181 @@ | ||||
| import { ContractFunctionObj } from '@0x/base-contract'; | ||||
| import { BigNumber, decodeBytesAsRevertError, logUtils } from '@0x/utils'; | ||||
|  | ||||
| import { ERC20BridgeSamplerContract } from '../../wrappers'; | ||||
|  | ||||
| import { ERC20BridgeSource, FillData, MeasuredSamplerResult, MeasuredSourceQuoteOperation } from './types'; | ||||
|  | ||||
| export type Parameters<T> = T extends (...args: infer TArgs) => any ? TArgs : never; | ||||
|  | ||||
| export interface MeasuredSamplerContractCall< | ||||
|     TFunc extends (...args: any[]) => ContractFunctionObj<any>, | ||||
|     TFillData extends FillData = FillData | ||||
| > { | ||||
|     contract: ERC20BridgeSamplerContract; | ||||
|     function: TFunc; | ||||
|     params: Parameters<TFunc>; | ||||
|     callback?: (callResults: string, fillData: TFillData) => MeasuredSamplerResult; | ||||
| } | ||||
|  | ||||
| class PathDeregister { | ||||
|     private static _instance: PathDeregister; | ||||
|     // Presence in this registry with a negtive number indicates the Path has been deregistered | ||||
|     private readonly _registry: { [key in ERC20BridgeSource]?: { [key: string]: number } } = {}; | ||||
|     private readonly _MAX_RESULTS = 100; | ||||
|  | ||||
|     public static createKey(args: any[]): string { | ||||
|         return args | ||||
|             .map(a => { | ||||
|                 if (typeof a === 'object' && a !== null) { | ||||
|                     return Object.values(a).join('-'); | ||||
|                 } | ||||
|                 if (Array.isArray(a)) { | ||||
|                     return a.join('-'); | ||||
|                 } | ||||
|                 return a.toString(); | ||||
|             }) | ||||
|             .join('-'); | ||||
|     } | ||||
|  | ||||
|     public static getInstance(): PathDeregister { | ||||
|         if (!PathDeregister._instance) { | ||||
|             PathDeregister._instance = new PathDeregister(); | ||||
|         } | ||||
|         return PathDeregister._instance; | ||||
|     } | ||||
|  | ||||
|     private static _getRandom(): number { | ||||
|         // tslint:disable-next-line: custom-no-magic-numbers | ||||
|         return Math.floor(Math.random() * (100 - 0 + 1)) + 0; | ||||
|     } | ||||
|  | ||||
|     public isDeregistered(source: ERC20BridgeSource, key: string): boolean { | ||||
|         if (!this._registry[source]) { | ||||
|             this._registry[source] = {}; | ||||
|         } | ||||
|         // Randomly allow the ops to be re-registered | ||||
|         if (PathDeregister._getRandom() === 1) { | ||||
|             return false; | ||||
|         } | ||||
|         return this._registry[source]![key] < 0; | ||||
|     } | ||||
|  | ||||
|     // Registers a successful result. Upon having one single success | ||||
|     // a Path is no longer deregistered | ||||
|     public handleResult(source: ERC20BridgeSource, key: string, result: MeasuredSamplerResult): void { | ||||
|         if (!this._registry[source]) { | ||||
|             this._registry[source] = {}; | ||||
|         } | ||||
|  | ||||
|         // Defaults to 0 | ||||
|         if (!this._registry[source]![key]) { | ||||
|             this._registry[source]![key] = 0; | ||||
|         } | ||||
|  | ||||
|         if (this._didSucceed(result)) { | ||||
|             if (this._registry[source]![key] < 0) { | ||||
|                 this._registry[source]![key] = 0; | ||||
|             } | ||||
|             this._registry[source]![key] = Math.min(this._MAX_RESULTS, this._registry[source]![key] + 1); | ||||
|         } else { | ||||
|             this._registry[source]![key] = Math.max(-this._MAX_RESULTS, this._registry[source]![key] - 1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // tslint:disable-next-line: prefer-function-over-method | ||||
|     private _didSucceed(result: MeasuredSamplerResult): boolean { | ||||
|         const nonZeroSample = result.samples.find(s => s.isGreaterThan(0)); | ||||
|         return nonZeroSample !== undefined && result.samples.length > 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // tslint:disable-next-line: max-classes-per-file | ||||
| export class MeasuredSamplerContractOperation< | ||||
|     TFunc extends (...args: any[]) => ContractFunctionObj<any>, | ||||
|     TFillData extends FillData = FillData | ||||
| > implements MeasuredSourceQuoteOperation<TFillData> { | ||||
|     public readonly source: ERC20BridgeSource; | ||||
|     public fillData: TFillData; | ||||
|     private readonly _samplerContract: ERC20BridgeSamplerContract; | ||||
|     private readonly _samplerFunction: TFunc; | ||||
|     private readonly _params: Parameters<TFunc>; | ||||
|     private readonly _callback?: (callResults: string, fillData: TFillData) => MeasuredSamplerResult; | ||||
|     private readonly _deregisterKey: string | undefined; | ||||
|     private readonly _deregisterable: boolean; | ||||
|     private readonly _log: boolean; | ||||
|  | ||||
|     constructor( | ||||
|         opts: { | ||||
|             source: ERC20BridgeSource; | ||||
|             fillData?: TFillData; | ||||
|             deregisterable?: boolean; | ||||
|             log?: boolean; | ||||
|         } & MeasuredSamplerContractCall<TFunc, TFillData>, | ||||
|     ) { | ||||
|         this.source = opts.source; | ||||
|         this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion | ||||
|         this._samplerContract = opts.contract; | ||||
|         this._samplerFunction = opts.function; | ||||
|         this._params = opts.params; | ||||
|         this._callback = opts.callback; | ||||
|         this._deregisterable = opts.deregisterable || false; | ||||
|         this._log = opts.log || false; | ||||
|         if (this._deregisterable) { | ||||
|             this._deregisterKey = PathDeregister.createKey(this._params.slice(0, this._params.length - 1)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public encodeCall(): string { | ||||
|         return this._samplerFunction | ||||
|             .bind(this._samplerContract)(...this._params) | ||||
|             .getABIEncodedTransactionData(); | ||||
|     } | ||||
|  | ||||
|     public handleCallResults(callResults: string): MeasuredSamplerResult { | ||||
|         let result: MeasuredSamplerResult; | ||||
|         if (this._callback !== undefined) { | ||||
|             result = this._callback(callResults, this.fillData); | ||||
|         } else { | ||||
|             const [gasUsed, samples] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], BigNumber[]]>( | ||||
|                 this._samplerFunction.name, | ||||
|                 callResults, | ||||
|             ); | ||||
|             result = { gasUsed, samples }; | ||||
|         } | ||||
|         if (this._deregisterKey) { | ||||
|             PathDeregister.getInstance().handleResult(this.source, this._deregisterKey, result); | ||||
|         } | ||||
|         if (this._log) { | ||||
|             logUtils.log({ source: this.source, fillData: this.fillData, params: this._params, ...result }); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public handleRevert(callResults: string): MeasuredSamplerResult { | ||||
|         let msg = callResults; | ||||
|         try { | ||||
|             msg = decodeBytesAsRevertError(callResults).toString(); | ||||
|         } catch (e) { | ||||
|             // do nothing | ||||
|         } | ||||
|         logUtils.warn( | ||||
|             `SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg} ${JSON.stringify( | ||||
|                 this.fillData, | ||||
|                 null, | ||||
|                 2, | ||||
|             )}`, | ||||
|         ); | ||||
|         const result = { gasUsed: [], samples: [] }; | ||||
|         if (this._deregisterKey) { | ||||
|             PathDeregister.getInstance().handleResult(this.source, this._deregisterKey, result); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public isDeregistered(): boolean { | ||||
|         if (this._deregisterKey) { | ||||
|             return PathDeregister.getInstance().isDeregistered(this.source, this._deregisterKey); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -5,14 +5,7 @@ import { Omit } from '../../types'; | ||||
|  | ||||
| import { ZERO_AMOUNT } from './constants'; | ||||
| import { getTwoHopAdjustedRate } from './rate_utils'; | ||||
| import { | ||||
|     DexSample, | ||||
|     ExchangeProxyOverhead, | ||||
|     FeeSchedule, | ||||
|     MarketSideLiquidity, | ||||
|     MultiHopFillData, | ||||
|     TokenAdjacencyGraph, | ||||
| } from './types'; | ||||
| import { DexSample, ExchangeProxyOverhead, MarketSideLiquidity, MultiHopFillData, TokenAdjacencyGraph } from './types'; | ||||
|  | ||||
| /** | ||||
|  * Given a token pair, returns the intermediate tokens to consider for two-hop routes. | ||||
| @@ -36,7 +29,7 @@ export function getIntermediateTokens( | ||||
|  */ | ||||
| export function getBestTwoHopQuote( | ||||
|     marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>, | ||||
|     feeSchedule?: FeeSchedule, | ||||
|     gasPrice: BigNumber, | ||||
|     exchangeProxyOverhead?: ExchangeProxyOverhead, | ||||
| ): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } { | ||||
|     const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity; | ||||
| @@ -57,7 +50,7 @@ export function getBestTwoHopQuote( | ||||
|     } | ||||
|     const best = filteredQuotes | ||||
|         .map(quote => | ||||
|             getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead), | ||||
|             getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, gasPrice, exchangeProxyOverhead), | ||||
|         ) | ||||
|         .reduce( | ||||
|             (prev, curr, i) => | ||||
| @@ -68,7 +61,7 @@ export function getBestTwoHopQuote( | ||||
|                     filteredQuotes[0], | ||||
|                     inputAmount, | ||||
|                     outputAmountPerEth, | ||||
|                     feeSchedule, | ||||
|                     gasPrice, | ||||
|                     exchangeProxyOverhead, | ||||
|                 ), | ||||
|                 quote: filteredQuotes[0], | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { AbiEncoder, BigNumber } from '@0x/utils'; | ||||
|  | ||||
| import { AssetSwapperContractAddresses, MarketOperation } from '../../types'; | ||||
|  | ||||
| import { MAX_UINT256, ZERO_AMOUNT } from './constants'; | ||||
| import { MAX_UINT256, NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED, ZERO_AMOUNT } from './constants'; | ||||
| import { | ||||
|     AggregationError, | ||||
|     BalancerFillData, | ||||
| @@ -61,6 +61,7 @@ export function createOrdersFromTwoHopSample( | ||||
|         output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output, | ||||
|         subFills: [], | ||||
|         fillData: firstHopSource.fillData, | ||||
|         gasUsed: ZERO_AMOUNT, | ||||
|     }; | ||||
|     const secondHopFill: CollapsedFill = { | ||||
|         sourcePathId: '', | ||||
| @@ -70,6 +71,7 @@ export function createOrdersFromTwoHopSample( | ||||
|         output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256, | ||||
|         subFills: [], | ||||
|         fillData: secondHopSource.fillData, | ||||
|         gasUsed: sample.gasUsed, | ||||
|     }; | ||||
|     return [ | ||||
|         createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side), | ||||
| @@ -329,6 +331,7 @@ export function createBridgeOrder( | ||||
|         sourcePathId: fill.sourcePathId, | ||||
|         type: FillQuoteTransformerOrderType.Bridge, | ||||
|         fills: [fill], | ||||
|         gasUsed: fill.gasUsed, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -487,6 +490,16 @@ export function createNativeOptimizedOrder( | ||||
|         fillData, | ||||
|     }; | ||||
|     return fill.type === FillQuoteTransformerOrderType.Rfq | ||||
|         ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData } | ||||
|         : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData }; | ||||
|         ? { | ||||
|               ...base, | ||||
|               type: FillQuoteTransformerOrderType.Rfq, | ||||
|               fillData: fillData as NativeRfqOrderFillData, | ||||
|               gasUsed: NATIVE_RFQT_GAS_USED, | ||||
|           } | ||||
|         : { | ||||
|               ...base, | ||||
|               type: FillQuoteTransformerOrderType.Limit, | ||||
|               fillData: fillData as NativeLimitOrderFillData, | ||||
|               gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|           }; | ||||
| } | ||||
|   | ||||
| @@ -230,7 +230,7 @@ export class Path { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     private _collapseFills(): ReadonlyArray<CollapsedFill> { | ||||
|     public _collapseFills(): ReadonlyArray<CollapsedFill> { | ||||
|         this.collapsedFills = []; | ||||
|         for (const fill of this.fills) { | ||||
|             const source = fill.source; | ||||
| @@ -241,6 +241,8 @@ export class Path { | ||||
|                     prevFill.input = prevFill.input.plus(fill.input); | ||||
|                     prevFill.output = prevFill.output.plus(fill.output); | ||||
|                     prevFill.fillData = fill.fillData; | ||||
|                     // Gas Used is always increasing | ||||
|                     prevFill.gasUsed = fill.gasUsed; | ||||
|                     prevFill.subFills.push(fill); | ||||
|                     continue; | ||||
|                 } | ||||
| @@ -252,6 +254,7 @@ export class Path { | ||||
|                 fillData: fill.fillData, | ||||
|                 input: fill.input, | ||||
|                 output: fill.output, | ||||
|                 gasUsed: fill.gasUsed, | ||||
|                 subFills: [fill], | ||||
|             }); | ||||
|         } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { BigNumber } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
| import { performance } from 'perf_hooks'; | ||||
|  | ||||
| import { MarketOperation } from '../../types'; | ||||
|  | ||||
| @@ -10,6 +11,180 @@ import { ERC20BridgeSource, Fill } from './types'; | ||||
|  | ||||
| const RUN_LIMIT_DECAY_FACTOR = 0.5; | ||||
|  | ||||
| const util = require('util'); | ||||
| const { route } = require('neon-router'); | ||||
|  | ||||
| const inputAccum = (f: Fill): BigNumber => (f.parent ? f.input.plus(inputAccum(f.parent)) : f.input); | ||||
| const adjustedOutputAccum = (f: Fill): BigNumber => { | ||||
|     if (!f.parent) { | ||||
|         return f.adjustedOutput; | ||||
|     } | ||||
|     const adjustedOutputParentAcc = adjustedOutputAccum(f.parent); | ||||
|     const feePerc = f.parent.adjustedOutput.dividedBy(f.parent.output); | ||||
|     return feePerc.times(f.output).plus(adjustedOutputParentAcc); | ||||
| }; | ||||
|  | ||||
| export function findOptimalRustPath(input: BigNumber, allFills: Fill[][], optimal: Path): Path { | ||||
|     // Track sample id's to integers (required by rust router) | ||||
|     const sampleIdLookup: { [key: string]: number } = {}; | ||||
|     let sampleIdCounter = 0; | ||||
|     const fillToSampleId = (s: { source: string; sourcePathId: string; index: number }): number => { | ||||
|         const key = `${s.source}-${s.sourcePathId}-${s.index}`; | ||||
|         if (sampleIdLookup[key]) { | ||||
|             return sampleIdLookup[key]; | ||||
|         } else { | ||||
|             sampleIdLookup[key] = ++sampleIdCounter; | ||||
|             return sampleIdLookup[key]; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     const adjustedParsedFills = allFills.map(fills => { | ||||
|         const adjustedFills: Fill[] = []; | ||||
|         // Samples are turned into Fills | ||||
|         // Fills are dependent on their parent and have their parents information "subtracted" from them | ||||
|         // e.g a samples for [1,10,100] => [5,50,500] look like [1, 9, 91] => [5, 40, 400] | ||||
|         for (let i = 0; i < fills.length; i++) { | ||||
|             const parsedFill: Fill = { ...fills[i] }; | ||||
|             if (parsedFill.index !== 0) { | ||||
|                 const parent = adjustedFills[i - 1]; | ||||
|                 parsedFill.parent = parent; | ||||
|                 parsedFill.input = parsedFill.input.plus(parent.input); | ||||
|                 parsedFill.output = parsedFill.output.plus(parent.output); | ||||
|                 // Adjusted output is only modified for the first fill | ||||
|                 const feePerc = parent.adjustedOutput.dividedBy(parent.output); | ||||
|                 parsedFill.adjustedOutput = feePerc.times(parsedFill.output); | ||||
|             } | ||||
|             adjustedFills.push(parsedFill); | ||||
|         } | ||||
|         return adjustedFills; | ||||
|     }); | ||||
|  | ||||
|     const pathsIn = adjustedParsedFills.map((fs: any) => ({ | ||||
|         ids: fs.map((f: any) => fillToSampleId(f)), | ||||
|         inputs: fs.map((f: any) => parseInt(f.input.toString())), | ||||
|         outputs: fs.map((f: any, i: number) => parseInt(f.adjustedOutput.toString())), | ||||
|     })); | ||||
|  | ||||
|     const pathOut = { | ||||
|         ids: optimal.fills.map(s => fillToSampleId(s)), | ||||
|         inputs: optimal.fills.map(s => parseInt(inputAccum(s).toString())), | ||||
|         outputs: optimal.fills.map(s => parseInt(adjustedOutputAccum(s).toString())), | ||||
|     }; | ||||
|  | ||||
|     const rustArgs = { | ||||
|         side: 'Sell', | ||||
|         // HACK: There can be off by 1 errors, somewhere... | ||||
|         targetInput: parseInt(input.plus(1).toString()), | ||||
|         pathsIn, | ||||
|         pathOut, | ||||
|     }; | ||||
|     // console.log(util.inspect({ rustArgs }, { depth: null })); | ||||
|  | ||||
|     const before = performance.now(); | ||||
|     const rustRoute: number[] = route(rustArgs); | ||||
|     console.log('Rust perf (real):', performance.now() - before); | ||||
|  | ||||
|     // Our route as input and perc | ||||
|     const optimalFillsByPathId = _.groupBy(optimal._collapseFills(), o => o.sourcePathId); | ||||
|     const fillsByPathId = _.groupBy(_.flatten(adjustedParsedFills), o => o.sourcePathId); | ||||
|     const out: BigNumber[] = []; | ||||
|     const outPerc: BigNumber[] = []; | ||||
|     for (const sourcePathId of Object.keys(fillsByPathId)) { | ||||
|         const fs = optimalFillsByPathId[sourcePathId]; | ||||
|         if (fs) { | ||||
|             outPerc.push( | ||||
|                 fs | ||||
|                     .reduce((prev, curr) => prev.plus(curr.input), new BigNumber(0)) | ||||
|                     .dividedBy(input) | ||||
|                     .times(100), | ||||
|             ); | ||||
|             out.push(fs.reduce((prev, curr) => prev.plus(curr.input), new BigNumber(0))); | ||||
|         } else { | ||||
|             outPerc.push(new BigNumber(0)); | ||||
|             out.push(new BigNumber(0)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const sourcePathKeys = Object.keys(fillsByPathId); | ||||
|     const fakeFills: Fill[] = []; | ||||
|     const totalInputs = BigNumber.sum(...rustRoute); | ||||
|     for (let i = 0; i < rustRoute.length; i++) { | ||||
|         if (rustRoute[i] === 0) { | ||||
|             continue; | ||||
|         } | ||||
|         const rustInput = new BigNumber(rustRoute[i]); | ||||
|         // HACK: Handle the case where the router can under quote the input | ||||
|         // Set the first fill just a tad higher | ||||
|         const adjInput = | ||||
|             totalInputs.lt(input) && fakeFills.length === 0 ? rustInput.plus(input.minus(totalInputs)) : rustInput; | ||||
|         // Rust router has chosen this source; | ||||
|         const sourcePathKey = sourcePathKeys[i]; | ||||
|         const fills = fillsByPathId[sourcePathKey]; | ||||
|         let fill = fills[fills.length - 1]; | ||||
|         // Descend to approach a closer fill for fillData which may not be consistent | ||||
|         // throughout the path (UniswapV3) and for a closer guesstimate at | ||||
|         // gas used | ||||
|         for (let k = fills.length - 1; k >= 0; k--) { | ||||
|             if (k === 0) { | ||||
|                 fill = fills[0]; | ||||
|             } | ||||
|             if (rustInput.isGreaterThan(fills[k].input)) { | ||||
|                 // Between here and the previous fill | ||||
|                 // HACK: Use the midpoint between the two | ||||
|                 const left = fills[k]; | ||||
|                 const right = fills[k + 1]; | ||||
|                 if (left && right) { | ||||
|                     const midPrice = left.output | ||||
|                         .dividedBy(left.input) | ||||
|                         .plus(right.output.dividedBy(right.input)) | ||||
|                         .dividedBy(2); | ||||
|                     fill = { | ||||
|                         ...right, // default to the greater (for gas used) | ||||
|                         input: rustInput, | ||||
|                         output: midPrice.times(rustInput).decimalPlaces(0), | ||||
|                     }; | ||||
|                 } else { | ||||
|                     fill = left || right; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         const adjustedOutput = fill.output | ||||
|             .dividedBy(fill.input) | ||||
|             .times(adjInput) | ||||
|             .decimalPlaces(0); | ||||
|         fakeFills.push({ | ||||
|             ...fill, | ||||
|             input: adjInput, | ||||
|             output: adjustedOutput, | ||||
|             index: 0, | ||||
|             parent: undefined, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     const fakePath = Path.create(MarketOperation.Sell, fakeFills, input); | ||||
|  | ||||
|     console.log( | ||||
|         util.inspect( | ||||
|             { | ||||
|                 // fillsByPathId: Object.keys(fillsByPathId).map(k => `${fillsByPathId[k][0].source}-${k}`), | ||||
|                 rustPerc: rustRoute.map(n => | ||||
|                     new BigNumber(n) | ||||
|                         .dividedBy(input) | ||||
|                         .times(100) | ||||
|                         .decimalPlaces(2), | ||||
|                 ), | ||||
|                 ourPerc: outPerc.map(o => o.decimalPlaces(2)), | ||||
|                 // rustRoute, | ||||
|                 // fakePath: fakePath._collapseFills(), | ||||
|             }, | ||||
|             { depth: null }, | ||||
|         ), | ||||
|     ); | ||||
|  | ||||
|     return fakePath; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Find the optimal mixture of fills that maximizes (for sells) or minimizes | ||||
|  * (for buys) output, while meeting the input requirement. | ||||
| @@ -21,6 +196,7 @@ export async function findOptimalPathAsync( | ||||
|     runLimit: number = 2 ** 8, | ||||
|     opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS, | ||||
| ): Promise<Path | undefined> { | ||||
|     let before = performance.now(); | ||||
|     // Sort fill arrays by descending adjusted completed rate. | ||||
|     // Remove any paths which cannot impact the optimal path | ||||
|     const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side); | ||||
| @@ -34,6 +210,15 @@ export async function findOptimalPathAsync( | ||||
|         // Yield to event loop. | ||||
|         await Promise.resolve(); | ||||
|     } | ||||
|  | ||||
|     console.log('TS perf:', performance.now() - before); | ||||
|     before = performance.now(); | ||||
|     const rustPath = findOptimalRustPath(targetInput, fills, optimalPath); | ||||
|     console.log('Rust perf:', performance.now() - before); | ||||
|     if (process.env.RUST === 'true') { | ||||
|         return rustPath; | ||||
|     } | ||||
|  | ||||
|     return optimalPath.isComplete() ? optimalPath : undefined; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils'; | ||||
| import { MarketOperation } from '../../types'; | ||||
|  | ||||
| import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants'; | ||||
| import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types'; | ||||
| import { DexSample, ExchangeProxyOverhead, MultiHopFillData } from './types'; | ||||
|  | ||||
| // tslint:disable:no-bitwise | ||||
|  | ||||
| @@ -16,20 +16,19 @@ export function getTwoHopAdjustedRate( | ||||
|     twoHopQuote: DexSample<MultiHopFillData>, | ||||
|     targetInput: BigNumber, | ||||
|     outputAmountPerEth: BigNumber, | ||||
|     fees: FeeSchedule = {}, | ||||
|     gasPrice: BigNumber, | ||||
|     exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT, | ||||
| ): BigNumber { | ||||
|     const { output, input, fillData } = twoHopQuote; | ||||
|     if (input.isLessThan(targetInput) || output.isZero()) { | ||||
|         return ZERO_AMOUNT; | ||||
|     } | ||||
|     const penalty = outputAmountPerEth.times( | ||||
|         exchangeProxyOverhead( | ||||
|             SOURCE_FLAGS.MultiHop | | ||||
|                 SOURCE_FLAGS[fillData.firstHopSource.source] | | ||||
|                 SOURCE_FLAGS[fillData.secondHopSource.source], | ||||
|         ).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)), | ||||
|     ); | ||||
|     const costInEth = exchangeProxyOverhead( | ||||
|         SOURCE_FLAGS.MultiHop | | ||||
|             SOURCE_FLAGS[fillData.firstHopSource.source] | | ||||
|             SOURCE_FLAGS[fillData.secondHopSource.source], | ||||
|     ).plus(twoHopQuote.gasUsed.times(gasPrice)); | ||||
|     const penalty = outputAmountPerEth.times(costInEth); | ||||
|     const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); | ||||
|     return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { ChainId } from '@0x/contract-addresses'; | ||||
| import { BigNumber, NULL_BYTES } from '@0x/utils'; | ||||
| import _ = require('lodash'); | ||||
|  | ||||
| import { SamplerOverrides } from '../../types'; | ||||
| import { ERC20BridgeSamplerContract } from '../../wrappers'; | ||||
| @@ -141,11 +142,12 @@ export class DexOrderSampler extends SamplerOperations { | ||||
|      * Run a series of operations from `DexOrderSampler.ops` in a single transaction. | ||||
|      * Takes an arbitrary length array, but is not typesafe. | ||||
|      */ | ||||
|     public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(ops: T): Promise<any[]> { | ||||
|     public async executeBatchAsync<T extends Array<BatchedOperation<any>>>( | ||||
|         ops: T, | ||||
|         opts: Partial<SamplerOverrides> = {}, | ||||
|     ): Promise<any[]> { | ||||
|         const callDatas = ops.map(o => o.encodeCall()); | ||||
|         const { overrides, block } = this._samplerOverrides | ||||
|             ? this._samplerOverrides | ||||
|             : { overrides: undefined, block: undefined }; | ||||
|         const { overrides, block } = _.merge(opts, this._samplerOverrides); | ||||
|  | ||||
|         // All operations are NOOPs | ||||
|         if (callDatas.every(cd => cd === NULL_BYTES)) { | ||||
| @@ -157,11 +159,12 @@ export class DexOrderSampler extends SamplerOperations { | ||||
|             .callAsync({ overrides }, block); | ||||
|         // Return the parsed results. | ||||
|         let rawCallResultsIdx = 0; | ||||
|         return callDatas.map((callData, i) => { | ||||
|         const results = callDatas.map((callData, i) => { | ||||
|             // tslint:disable-next-line:boolean-naming | ||||
|             const { data, success } = | ||||
|                 callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : { success: true, data: NULL_BYTES }; | ||||
|             return success ? ops[i].handleCallResults(data) : ops[i].handleRevert(data); | ||||
|         }); | ||||
|         return results; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -56,7 +56,13 @@ export class SamplerContractOperation< | ||||
|         } catch (e) { | ||||
|             // do nothing | ||||
|         } | ||||
|         logUtils.warn(`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg}`); | ||||
|         logUtils.warn( | ||||
|             `SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg} ${JSON.stringify( | ||||
|                 this.fillData, | ||||
|                 null, | ||||
|                 2, | ||||
|             )}`, | ||||
|         ); | ||||
|         return []; | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -97,21 +97,13 @@ export enum CurveFunctionSelectors { | ||||
|     None = '0x00000000', | ||||
|     exchange = '0x3df02124', | ||||
|     exchange_underlying = '0xa6417ed6', | ||||
|     get_dy_underlying = '0x07211ef7', | ||||
|     get_dx_underlying = '0x0e71d1b9', | ||||
|     get_dy = '0x5e0d443f', | ||||
|     get_dx = '0x67df02ca', | ||||
|     // Curve V2 | ||||
|     exchange_v2 = '0x5b41b908', | ||||
|     exchange_underlying_v2 = '0x65b2489b', | ||||
|     get_dy_v2 = '0x556d6e9f', | ||||
|     get_dy_underlying_v2 = '0x85f11d1e', | ||||
|     // Smoothy | ||||
|     swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256) | ||||
|     get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256) | ||||
|     // Nerve BSC, Saddle Mainnet | ||||
|     swap = '0x91695586', // swap(uint8,uint8,uint256,uint256,uint256) | ||||
|     calculateSwap = '0xa95b089f', // calculateSwap(uint8,uint8,uint256) | ||||
| } | ||||
| // tslint:enable: enum-naming | ||||
|  | ||||
| @@ -120,8 +112,6 @@ export enum CurveFunctionSelectors { | ||||
|  */ | ||||
| export interface CurveInfo { | ||||
|     exchangeFunctionSelector: CurveFunctionSelectors; | ||||
|     sellQuoteFunctionSelector: CurveFunctionSelectors; | ||||
|     buyQuoteFunctionSelector: CurveFunctionSelectors; | ||||
|     poolAddress: string; | ||||
|     tokens: string[]; | ||||
|     metaTokens: string[] | undefined; | ||||
| @@ -167,7 +157,9 @@ export interface DexSample<TFillData extends FillData = FillData> { | ||||
|     fillData: TFillData; | ||||
|     input: BigNumber; | ||||
|     output: BigNumber; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface CurveFillData extends FillData { | ||||
|     fromTokenIdx: number; | ||||
|     toTokenIdx: number; | ||||
| @@ -223,8 +215,8 @@ export interface GenericRouterFillData extends FillData { | ||||
| } | ||||
|  | ||||
| export interface MultiHopFillData extends FillData { | ||||
|     firstHopSource: SourceQuoteOperation; | ||||
|     secondHopSource: SourceQuoteOperation; | ||||
|     firstHopSource: MeasuredSourceQuoteOperation; | ||||
|     secondHopSource: MeasuredSourceQuoteOperation; | ||||
|     intermediateToken: string; | ||||
| } | ||||
|  | ||||
| @@ -285,6 +277,7 @@ export interface Fill<TFillData extends FillData = FillData> { | ||||
|     parent?: Fill; | ||||
|     // The index of the fill in the original path. | ||||
|     index: number; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -313,6 +306,7 @@ export interface CollapsedFill<TFillData extends FillData = FillData> { | ||||
|         input: BigNumber; | ||||
|         output: BigNumber; | ||||
|     }>; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -329,6 +323,7 @@ export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> | ||||
|     makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill | ||||
|     takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill | ||||
|     fills: CollapsedFill[]; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData> | ||||
| @@ -336,6 +331,7 @@ export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillDat | ||||
|     type: FillQuoteTransformerOrderType.Bridge; | ||||
|     fillData: TFillData; | ||||
|     sourcePathId: string; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> { | ||||
| @@ -361,8 +357,6 @@ export interface GetMarketOrdersRfqOpts extends RfqRequestOpts { | ||||
|     firmQuoteValidator?: RfqFirmQuoteValidator; | ||||
| } | ||||
|  | ||||
| export type FeeEstimate = (fillData: FillData) => number | BigNumber; | ||||
| export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>; | ||||
| export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber; | ||||
|  | ||||
| /** | ||||
| @@ -416,13 +410,9 @@ export interface GetMarketOrdersOpts { | ||||
|      */ | ||||
|     sampleDistributionBase: number; | ||||
|     /** | ||||
|      * Fees for each liquidity source, expressed in gas. | ||||
|      * The gas overhead of various execution paths | ||||
|      * E.g Uniswap VIP overhead, FlashWallet overhead | ||||
|      */ | ||||
|     feeSchedule: FeeSchedule; | ||||
|     /** | ||||
|      * Estimated gas consumed by each liquidity source. | ||||
|      */ | ||||
|     gasSchedule: FeeSchedule; | ||||
|     exchangeProxyOverhead: ExchangeProxyOverhead; | ||||
|     /** | ||||
|      * Whether to pad the quote with a redundant fallback quote using different | ||||
| @@ -447,6 +437,10 @@ export interface GetMarketOrdersOpts { | ||||
|      * hopping to. E.g DAI->USDC via an adjacent token WETH | ||||
|      */ | ||||
|     tokenAdjacencyGraph: TokenAdjacencyGraph; | ||||
|     /** | ||||
|      * The current gas price. Used to adjust pricing by the cost of a DEX | ||||
|      */ | ||||
|     gasPrice: BigNumber; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -458,6 +452,18 @@ export interface BatchedOperation<TResult> { | ||||
|     handleRevert(callResults: string): TResult; | ||||
| } | ||||
|  | ||||
| export interface MeasuredSamplerResult { | ||||
|     gasUsed: BigNumber[]; | ||||
|     samples: BigNumber[]; | ||||
| } | ||||
|  | ||||
| export interface MeasuredSourceQuoteOperation<TFillData extends FillData = FillData> | ||||
|     extends BatchedOperation<MeasuredSamplerResult> { | ||||
|     readonly source: ERC20BridgeSource; | ||||
|     fillData: TFillData; | ||||
|     isDeregistered?: () => boolean; | ||||
| } | ||||
|  | ||||
| export interface SourceQuoteOperation<TFillData extends FillData = FillData> extends BatchedOperation<BigNumber[]> { | ||||
|     readonly source: ERC20BridgeSource; | ||||
|     fillData: TFillData; | ||||
| @@ -526,10 +532,10 @@ export interface GenerateOptimizedOrdersOpts { | ||||
|     bridgeSlippage?: number; | ||||
|     maxFallbackSlippage?: number; | ||||
|     excludedSources?: ERC20BridgeSource[]; | ||||
|     feeSchedule?: FeeSchedule; | ||||
|     exchangeProxyOverhead?: ExchangeProxyOverhead; | ||||
|     allowFallback?: boolean; | ||||
|     shouldBatchBridgeOrders?: boolean; | ||||
|     gasPrice: BigNumber; | ||||
| } | ||||
|  | ||||
| export interface ComparisonPrice { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import _ = require('lodash'); | ||||
|  | ||||
| import { MarketOperation, NativeOrderWithFillableAmounts } from '../types'; | ||||
|  | ||||
| import { NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED } from './market_operation_utils/constants'; | ||||
| import { | ||||
|     CollapsedFill, | ||||
|     DexSample, | ||||
| @@ -22,6 +23,7 @@ export interface QuoteReportEntryBase { | ||||
|     makerAmount: BigNumber; | ||||
|     takerAmount: BigNumber; | ||||
|     fillData: FillData; | ||||
|     gasUsed: BigNumber; | ||||
| } | ||||
| export interface BridgeQuoteReportEntry extends QuoteReportEntryBase { | ||||
|     liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>; | ||||
| @@ -140,6 +142,7 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp | ||||
|             takerAmount: ds.output, | ||||
|             liquiditySource, | ||||
|             fillData: ds.fillData, | ||||
|             gasUsed: ds.gasUsed, | ||||
|         }; | ||||
|     } else if (marketOperation === MarketOperation.Sell) { | ||||
|         return { | ||||
| @@ -147,6 +150,7 @@ export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOp | ||||
|             takerAmount: ds.input, | ||||
|             liquiditySource, | ||||
|             fillData: ds.fillData, | ||||
|             gasUsed: ds.gasUsed, | ||||
|         }; | ||||
|     } else { | ||||
|         throw new Error(`Unexpected marketOperation ${marketOperation}`); | ||||
| @@ -171,6 +175,7 @@ export function multiHopSampleToReportSource( | ||||
|             takerAmount: ds.output, | ||||
|             fillData: ds.fillData, | ||||
|             hopSources: [firstHop.source, secondHop.source], | ||||
|             gasUsed: ds.gasUsed, | ||||
|         }; | ||||
|     } else if (marketOperation === MarketOperation.Sell) { | ||||
|         return { | ||||
| @@ -179,6 +184,7 @@ export function multiHopSampleToReportSource( | ||||
|             takerAmount: ds.input, | ||||
|             fillData: ds.fillData, | ||||
|             hopSources: [firstHop.source, secondHop.source], | ||||
|             gasUsed: ds.gasUsed, | ||||
|         }; | ||||
|     } else { | ||||
|         throw new Error(`Unexpected marketOperation ${marketOperation}`); | ||||
| @@ -223,6 +229,7 @@ export function nativeOrderToReportEntry( | ||||
|             ...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}), | ||||
|             nativeOrder, | ||||
|             fillData, | ||||
|             gasUsed: NATIVE_RFQT_GAS_USED, | ||||
|         }; | ||||
|     } else { | ||||
|         // tslint:disable-next-line: no-object-literal-type-assertion | ||||
| @@ -231,6 +238,7 @@ export function nativeOrderToReportEntry( | ||||
|             ...nativeOrderBase, | ||||
|             isRfqt: false, | ||||
|             fillData, | ||||
|             gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import { BigNumber } from '@0x/utils'; | ||||
| import { constants } from '../constants'; | ||||
| import { MarketOperation } from '../types'; | ||||
|  | ||||
| import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types'; | ||||
| import { NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types'; | ||||
| import { getNativeAdjustedTakerFeeAmount } from './utils'; | ||||
|  | ||||
| const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants; | ||||
| @@ -72,13 +72,11 @@ export interface QuoteFillInfo { | ||||
| } | ||||
|  | ||||
| export interface QuoteFillInfoOpts { | ||||
|     gasSchedule: FeeSchedule; | ||||
|     protocolFeeMultiplier: BigNumber; | ||||
|     slippage: number; | ||||
| } | ||||
|  | ||||
| const DEFAULT_SIMULATED_FILL_QUOTE_INFO_OPTS: QuoteFillInfoOpts = { | ||||
|     gasSchedule: {}, | ||||
|     protocolFeeMultiplier: PROTOCOL_FEE_MULTIPLIER, | ||||
|     slippage: 0, | ||||
| }; | ||||
| @@ -108,7 +106,6 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult | ||||
|         createBestCaseFillOrderCalls(quoteInfo), | ||||
|         quoteInfo.fillAmount, | ||||
|         protocolFeePerFillOrder, | ||||
|         opts.gasSchedule, | ||||
|     ); | ||||
|     return fromIntermediateQuoteFillResult(result, quoteInfo); | ||||
| } | ||||
| @@ -122,9 +119,9 @@ export function simulateWorstCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult | ||||
|     const protocolFeePerFillOrder = quoteInfo.gasPrice.times(opts.protocolFeeMultiplier); | ||||
|     const bestCase = createBestCaseFillOrderCalls(quoteInfo); | ||||
|     const result = { | ||||
|         ...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder, opts.gasSchedule), | ||||
|         ...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder), | ||||
|         // Worst case gas and protocol fee is hitting all orders. | ||||
|         gas: getTotalGasUsedByFills(quoteInfo.orders, opts.gasSchedule), | ||||
|         gas: getTotalGasUsedByFills(quoteInfo.orders), | ||||
|         protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.filter(o => hasProtocolFee(o)).length), | ||||
|     }; | ||||
|     // Adjust the output by 1-slippage for the worst case if it is a sell | ||||
| @@ -140,7 +137,6 @@ export function fillQuoteOrders( | ||||
|     fillOrders: QuoteFillOrderCall[], | ||||
|     inputAmount: BigNumber, | ||||
|     protocolFeePerFillOrder: BigNumber, | ||||
|     gasSchedule: FeeSchedule, | ||||
| ): IntermediateQuoteFillResult { | ||||
|     const result: IntermediateQuoteFillResult = { | ||||
|         ...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT, | ||||
| @@ -155,9 +151,8 @@ export function fillQuoteOrders( | ||||
|             if (remainingInput.lte(0)) { | ||||
|                 break; | ||||
|             } | ||||
|             const { source, fillData } = fill; | ||||
|             const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData); | ||||
|             result.gas += new BigNumber(gas).toNumber(); | ||||
|             const { source, gasUsed } = fill; | ||||
|             result.gas += new BigNumber(gasUsed).toNumber(); | ||||
|             result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT; | ||||
|  | ||||
|             // Actual rates are rarely linear, so fill subfills individually to | ||||
| @@ -314,11 +309,10 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI | ||||
|     }; | ||||
| } | ||||
|  | ||||
| function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number { | ||||
| function getTotalGasUsedByFills(fills: OptimizedMarketOrder[]): number { | ||||
|     let gasUsed = 0; | ||||
|     for (const f of fills) { | ||||
|         const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData); | ||||
|         gasUsed += new BigNumber(fee).toNumber(); | ||||
|         gasUsed += new BigNumber(f.gasUsed).toNumber(); | ||||
|     } | ||||
|     return gasUsed; | ||||
| } | ||||
|   | ||||
| @@ -4,5 +4,8 @@ | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../generated-wrappers/balance_checker'; | ||||
| export * from '../generated-wrappers/delegate_hacked_erc20'; | ||||
| export * from '../generated-wrappers/erc20_bridge_sampler'; | ||||
| export * from '../generated-wrappers/fake_taker'; | ||||
| export * from '../generated-wrappers/gas_overhead'; | ||||
| export * from '../generated-wrappers/hacked_erc20'; | ||||
|   | ||||
| @@ -5,28 +5,23 @@ | ||||
|  */ | ||||
| import { ContractArtifact } from 'ethereum-types'; | ||||
|  | ||||
| import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; | ||||
| import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json'; | ||||
| import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json'; | ||||
| import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json'; | ||||
| import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json'; | ||||
| import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; | ||||
| import * as CurveV2Sampler from '../test/generated-artifacts/CurveV2Sampler.json'; | ||||
| import * as DelegateHackedERC20 from '../test/generated-artifacts/DelegateHackedERC20.json'; | ||||
| import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json'; | ||||
| import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json'; | ||||
| import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json'; | ||||
| import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; | ||||
| import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json'; | ||||
| import * as FakeTaker from '../test/generated-artifacts/FakeTaker.json'; | ||||
| import * as IBalancer from '../test/generated-artifacts/IBalancer.json'; | ||||
| import * as IBancor from '../test/generated-artifacts/IBancor.json'; | ||||
| import * as ICurve from '../test/generated-artifacts/ICurve.json'; | ||||
| import * as GasOverhead from '../test/generated-artifacts/GasOverhead.json'; | ||||
| import * as HackedERC20 from '../test/generated-artifacts/HackedERC20.json'; | ||||
| import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; | ||||
| import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; | ||||
| import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json'; | ||||
| import * as IMStable from '../test/generated-artifacts/IMStable.json'; | ||||
| import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json'; | ||||
| import * as IShell from '../test/generated-artifacts/IShell.json'; | ||||
| import * as ISmoothy from '../test/generated-artifacts/ISmoothy.json'; | ||||
| import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; | ||||
| import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json'; | ||||
| import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json'; | ||||
| @@ -36,11 +31,9 @@ import * as LiquidityProviderSampler from '../test/generated-artifacts/Liquidity | ||||
| import * as MakerPSMSampler from '../test/generated-artifacts/MakerPSMSampler.json'; | ||||
| import * as MooniswapSampler from '../test/generated-artifacts/MooniswapSampler.json'; | ||||
| import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json'; | ||||
| import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json'; | ||||
| import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json'; | ||||
| import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json'; | ||||
| import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json'; | ||||
| import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json'; | ||||
| import * as SwapRevertSampler from '../test/generated-artifacts/SwapRevertSampler.json'; | ||||
| import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; | ||||
| import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json'; | ||||
| import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json'; | ||||
| @@ -49,17 +42,20 @@ import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler. | ||||
| import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json'; | ||||
| import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json'; | ||||
| export const artifacts = { | ||||
|     ApproximateBuys: ApproximateBuys as ContractArtifact, | ||||
|     BalanceChecker: BalanceChecker as ContractArtifact, | ||||
|     BalancerSampler: BalancerSampler as ContractArtifact, | ||||
|     BalancerV2Sampler: BalancerV2Sampler as ContractArtifact, | ||||
|     BancorSampler: BancorSampler as ContractArtifact, | ||||
|     CurveSampler: CurveSampler as ContractArtifact, | ||||
|     CurveV2Sampler: CurveV2Sampler as ContractArtifact, | ||||
|     DODOSampler: DODOSampler as ContractArtifact, | ||||
|     DODOV2Sampler: DODOV2Sampler as ContractArtifact, | ||||
|     DelegateHackedERC20: DelegateHackedERC20 as ContractArtifact, | ||||
|     ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, | ||||
|     Eth2DaiSampler: Eth2DaiSampler as ContractArtifact, | ||||
|     FakeTaker: FakeTaker as ContractArtifact, | ||||
|     GasOverhead: GasOverhead as ContractArtifact, | ||||
|     HackedERC20: HackedERC20 as ContractArtifact, | ||||
|     KyberDmmSampler: KyberDmmSampler as ContractArtifact, | ||||
|     KyberSampler: KyberSampler as ContractArtifact, | ||||
|     LidoSampler: LidoSampler as ContractArtifact, | ||||
| @@ -67,26 +63,16 @@ export const artifacts = { | ||||
|     MStableSampler: MStableSampler as ContractArtifact, | ||||
|     MakerPSMSampler: MakerPSMSampler as ContractArtifact, | ||||
|     MooniswapSampler: MooniswapSampler as ContractArtifact, | ||||
|     MultiBridgeSampler: MultiBridgeSampler as ContractArtifact, | ||||
|     NativeOrderSampler: NativeOrderSampler as ContractArtifact, | ||||
|     SamplerUtils: SamplerUtils as ContractArtifact, | ||||
|     ShellSampler: ShellSampler as ContractArtifact, | ||||
|     SmoothySampler: SmoothySampler as ContractArtifact, | ||||
|     SwapRevertSampler: SwapRevertSampler as ContractArtifact, | ||||
|     TwoHopSampler: TwoHopSampler as ContractArtifact, | ||||
|     UniswapSampler: UniswapSampler as ContractArtifact, | ||||
|     UniswapV2Sampler: UniswapV2Sampler as ContractArtifact, | ||||
|     UniswapV3Sampler: UniswapV3Sampler as ContractArtifact, | ||||
|     UtilitySampler: UtilitySampler as ContractArtifact, | ||||
|     IBalancer: IBalancer as ContractArtifact, | ||||
|     IBancor: IBancor as ContractArtifact, | ||||
|     ICurve: ICurve as ContractArtifact, | ||||
|     IEth2Dai: IEth2Dai as ContractArtifact, | ||||
|     IKyberNetwork: IKyberNetwork as ContractArtifact, | ||||
|     IMStable: IMStable as ContractArtifact, | ||||
|     IMooniswap: IMooniswap as ContractArtifact, | ||||
|     IMultiBridge: IMultiBridge as ContractArtifact, | ||||
|     IShell: IShell as ContractArtifact, | ||||
|     ISmoothy: ISmoothy as ContractArtifact, | ||||
|     IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, | ||||
|     IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact, | ||||
|     DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact, | ||||
|   | ||||
| @@ -18,7 +18,6 @@ const expect = chai.expect; | ||||
| const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f'; | ||||
| const ETH_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; | ||||
| const GAS_PRICE = new BigNumber(50e9); // 50 gwei | ||||
| const NATIVE_ORDER_FEE = new BigNumber(220e3); // 220K gas | ||||
|  | ||||
| // DEX samples to fill in MarketSideLiquidity | ||||
| const kyberSample1: DexSample = { | ||||
| @@ -26,19 +25,18 @@ const kyberSample1: DexSample = { | ||||
|     input: new BigNumber(10000), | ||||
|     output: new BigNumber(10001), | ||||
|     fillData: {}, | ||||
|     gasUsed: new BigNumber(450000), | ||||
| }; | ||||
| const uniswapSample1: DexSample = { | ||||
|     source: ERC20BridgeSource.UniswapV2, | ||||
|     input: new BigNumber(10003), | ||||
|     output: new BigNumber(10004), | ||||
|     fillData: {}, | ||||
|     gasUsed: new BigNumber(90000), | ||||
| }; | ||||
| const dexQuotes: DexSample[] = [kyberSample1, uniswapSample1]; | ||||
|  | ||||
| const feeSchedule = { | ||||
|     [ERC20BridgeSource.Native]: _.constant(GAS_PRICE.times(NATIVE_ORDER_FEE)), | ||||
| }; | ||||
|  | ||||
| // At this stage it has been gas price adjusted | ||||
| const exchangeProxyOverhead = (sourceFlags: bigint) => { | ||||
|     if ([SOURCE_FLAGS.RfqOrder].includes(sourceFlags)) { | ||||
|         return new BigNumber(20e3).times(GAS_PRICE); | ||||
| @@ -102,14 +100,14 @@ describe('getComparisonPrices', async () => { | ||||
|             adjustedRate, | ||||
|             AMOUNT, | ||||
|             sellMarketSideLiquidity, | ||||
|             feeSchedule, | ||||
|             GAS_PRICE, | ||||
|             exchangeProxyOverhead, | ||||
|         ); | ||||
|  | ||||
|         // expected outcome | ||||
|         const EXPECTED_PRICE = new BigNumber('500.6'); | ||||
|         const EXPECTED_PRICE = new BigNumber('500.30'); | ||||
|  | ||||
|         expect(comparisonPrices.wholeOrder).to.deep.eq(EXPECTED_PRICE); | ||||
|         expect(comparisonPrices.wholeOrder).to.be.bignumber.eq(EXPECTED_PRICE); | ||||
|     }); | ||||
|     it('should create a proper comparison price for Buys', () => { | ||||
|         // test buying 10 ETH with DAI | ||||
| @@ -124,14 +122,14 @@ describe('getComparisonPrices', async () => { | ||||
|             adjustedRate, | ||||
|             AMOUNT, | ||||
|             buyMarketSideLiquidity, | ||||
|             feeSchedule, | ||||
|             GAS_PRICE, | ||||
|             exchangeProxyOverhead, | ||||
|         ); | ||||
|  | ||||
|         // expected outcome | ||||
|         const EXPECTED_PRICE = new BigNumber('0.0020024029'); | ||||
|         const EXPECTED_PRICE = new BigNumber('0.0020012007'); | ||||
|  | ||||
|         expect(comparisonPrices.wholeOrder).to.deep.eq(EXPECTED_PRICE); | ||||
|         expect(comparisonPrices.wholeOrder).to.be.bignumber.eq(EXPECTED_PRICE); | ||||
|     }); | ||||
|     it('should not return a price if takerAmount is < 0', () => { | ||||
|         // test selling 0.00001 ETH for DAI | ||||
| @@ -144,7 +142,7 @@ describe('getComparisonPrices', async () => { | ||||
|             adjustedRate, | ||||
|             AMOUNT, | ||||
|             sellMarketSideLiquidity, | ||||
|             feeSchedule, | ||||
|             GAS_PRICE, | ||||
|             exchangeProxyOverhead, | ||||
|         ); | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,8 @@ import { blockchainTests, describe, expect, toBaseUnitAmount, Web3ProviderEngine | ||||
| import { RPCSubprovider } from '@0x/subproviders'; | ||||
| import { BigNumber, NULL_BYTES, providerUtils } from '@0x/utils'; | ||||
|  | ||||
| import { ERC20BridgeSource } from '../../src'; | ||||
| import { getCurveLikeInfosForPair } from '../../src/utils/market_operation_utils/bridge_source_utils'; | ||||
| import { KYBER_CONFIG_BY_CHAIN_ID, MAINNET_TOKENS } from '../../src/utils/market_operation_utils/constants'; | ||||
| import { artifacts } from '../artifacts'; | ||||
| import { ERC20BridgeSamplerContract } from '../wrappers'; | ||||
| @@ -30,27 +32,50 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { | ||||
|         }); | ||||
|     }); | ||||
|     describe('Curve', () => { | ||||
|         const CURVE_ADDRESS = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51'; | ||||
|         const DAI_TOKEN_INDEX = new BigNumber(0); | ||||
|         const USDC_TOKEN_INDEX = new BigNumber(1); | ||||
|         const CURVE_INFO = { | ||||
|             poolAddress: CURVE_ADDRESS, | ||||
|             sellQuoteFunctionSelector: '0x07211ef7', | ||||
|             buyQuoteFunctionSelector: '0x0e71d1b9', | ||||
|         }; | ||||
|  | ||||
|         describe('sampleSellsFromCurve()', () => { | ||||
|             it('samples sells from Curve DAI->USDC', async () => { | ||||
|                 const CURVE_INFO = getCurveLikeInfosForPair( | ||||
|                     ChainId.Mainnet, | ||||
|                     MAINNET_TOKENS.DAI, | ||||
|                     MAINNET_TOKENS.USDC, | ||||
|                     ERC20BridgeSource.Curve, | ||||
|                 )[0]; | ||||
|                 const samples = await testContract | ||||
|                     .sampleSellsFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)]) | ||||
|                     .sampleSellsFromCurve( | ||||
|                         { | ||||
|                             curveAddress: CURVE_INFO.poolAddress, | ||||
|                             fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx), | ||||
|                             toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx), | ||||
|                             exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector, | ||||
|                         }, | ||||
|                         MAINNET_TOKENS.DAI, | ||||
|                         MAINNET_TOKENS.USDC, | ||||
|                         [toBaseUnitAmount(1)], | ||||
|                     ) | ||||
|                     .callAsync({ overrides }); | ||||
|                 expect(samples.length).to.be.bignumber.greaterThan(0); | ||||
|                 expect(samples[0]).to.be.bignumber.greaterThan(0); | ||||
|             }); | ||||
|  | ||||
|             it('samples sells from Curve USDC->DAI', async () => { | ||||
|                 const CURVE_INFO = getCurveLikeInfosForPair( | ||||
|                     ChainId.Mainnet, | ||||
|                     MAINNET_TOKENS.USDC, | ||||
|                     MAINNET_TOKENS.DAI, | ||||
|                     ERC20BridgeSource.Curve, | ||||
|                 )[0]; | ||||
|                 const samples = await testContract | ||||
|                     .sampleSellsFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) | ||||
|                     .sampleSellsFromCurve( | ||||
|                         { | ||||
|                             curveAddress: CURVE_INFO.poolAddress, | ||||
|                             fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx), | ||||
|                             toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx), | ||||
|                             exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector, | ||||
|                         }, | ||||
|                         MAINNET_TOKENS.USDC, | ||||
|                         MAINNET_TOKENS.DAI, | ||||
|                         [toBaseUnitAmount(1, 6)], | ||||
|                     ) | ||||
|                     .callAsync({ overrides }); | ||||
|                 expect(samples.length).to.be.bignumber.greaterThan(0); | ||||
|                 expect(samples[0]).to.be.bignumber.greaterThan(0); | ||||
| @@ -58,11 +83,27 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { | ||||
|         }); | ||||
|  | ||||
|         describe('sampleBuysFromCurve()', () => { | ||||
|             const CURVE_INFO = getCurveLikeInfosForPair( | ||||
|                 ChainId.Mainnet, | ||||
|                 MAINNET_TOKENS.DAI, | ||||
|                 MAINNET_TOKENS.USDC, | ||||
|                 ERC20BridgeSource.Curve, | ||||
|             )[0]; | ||||
|             it('samples buys from Curve DAI->USDC', async () => { | ||||
|                 // From DAI to USDC | ||||
|                 // I want to buy 1 USDC | ||||
|                 const samples = await testContract | ||||
|                     .sampleBuysFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)]) | ||||
|                     .sampleBuysFromCurve( | ||||
|                         { | ||||
|                             curveAddress: CURVE_INFO.poolAddress, | ||||
|                             fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx), | ||||
|                             toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx), | ||||
|                             exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector, | ||||
|                         }, | ||||
|                         MAINNET_TOKENS.DAI, | ||||
|                         MAINNET_TOKENS.USDC, | ||||
|                         [toBaseUnitAmount(1, 6)], | ||||
|                     ) | ||||
|                     .callAsync({ overrides }); | ||||
|                 expect(samples.length).to.be.bignumber.greaterThan(0); | ||||
|                 expect(samples[0]).to.be.bignumber.greaterThan(0); | ||||
| @@ -72,7 +113,17 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { | ||||
|                 // From USDC to DAI | ||||
|                 // I want to buy 1 DAI | ||||
|                 const samples = await testContract | ||||
|                     .sampleBuysFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)]) | ||||
|                     .sampleBuysFromCurve( | ||||
|                         { | ||||
|                             curveAddress: CURVE_INFO.poolAddress, | ||||
|                             fromCoinIdx: new BigNumber(CURVE_INFO.takerTokenIdx), | ||||
|                             toCoinIdx: new BigNumber(CURVE_INFO.makerTokenIdx), | ||||
|                             exchangeFunctionSelector: CURVE_INFO.exchangeFunctionSelector, | ||||
|                         }, | ||||
|                         MAINNET_TOKENS.USDC, | ||||
|                         MAINNET_TOKENS.DAI, | ||||
|                         [toBaseUnitAmount(1)], | ||||
|                     ) | ||||
|                     .callAsync({ overrides }); | ||||
|                 expect(samples.length).to.be.bignumber.greaterThan(0); | ||||
|                 expect(samples[0]).to.be.bignumber.greaterThan(0); | ||||
|   | ||||
| @@ -18,7 +18,8 @@ import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from ' | ||||
| // tslint:disable: custom-no-magic-numbers | ||||
|  | ||||
| const { NULL_ADDRESS } = constants; | ||||
| blockchainTests('erc20-bridge-sampler', env => { | ||||
| // jacob: Skip until we can override in Ganache | ||||
| blockchainTests.skip('erc20-bridge-sampler', env => { | ||||
|     let testContract: TestERC20BridgeSamplerContract; | ||||
|     const RATE_DENOMINATOR = constants.ONE_ETHER; | ||||
|     const MIN_RATE = new BigNumber('0.01'); | ||||
| @@ -37,7 +38,6 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|     const KYBER_RESERVE_OFFSET = new BigNumber(0); | ||||
|     let KYBER_ADDRESS = ''; | ||||
|     let ETH2DAI_ADDRESS = ''; | ||||
|     let UNISWAP_ADDRESS = ''; | ||||
|     let UNISWAP_V2_ROUTER = ''; | ||||
|  | ||||
|     before(async () => { | ||||
| @@ -50,7 +50,6 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         UNISWAP_V2_ROUTER = await testContract.uniswapV2Router().callAsync(); | ||||
|         KYBER_ADDRESS = await testContract.kyber().callAsync(); | ||||
|         ETH2DAI_ADDRESS = await testContract.eth2Dai().callAsync(); | ||||
|         UNISWAP_ADDRESS = await testContract.uniswap().callAsync(); | ||||
|     }); | ||||
|  | ||||
|     function getPackedHash(...args: string[]): string { | ||||
| @@ -660,198 +659,6 @@ blockchainTests('erc20-bridge-sampler', env => { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     blockchainTests.resets('sampleSellsFromUniswap()', () => { | ||||
|         const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
|  | ||||
|         it('throws if tokens are the same', async () => { | ||||
|             const tx = testContract.sampleSellsFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); | ||||
|             return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); | ||||
|         }); | ||||
|  | ||||
|         it('can return no quotes', async () => { | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(TAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the taker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(nonExistantToken); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleSellsFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     blockchainTests.resets('sampleBuysFromUniswap()', () => { | ||||
|         const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; | ||||
|         before(async () => { | ||||
|             await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); | ||||
|         }); | ||||
|  | ||||
|         it('throws if tokens are the same', async () => { | ||||
|             const tx = testContract.sampleBuysFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); | ||||
|             return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); | ||||
|         }); | ||||
|  | ||||
|         it('can return no quotes', async () => { | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq([]); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote token -> ETH', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if token -> ETH fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('can quote ETH -> token', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if ETH -> token fails', async () => { | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             await enableFailTriggerAsync(); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the maker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(nonExistantToken); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|  | ||||
|         it('returns zero if no exchange exists for the taker token', async () => { | ||||
|             const nonExistantToken = randomAddress(); | ||||
|             const sampleAmounts = getSampleAmounts(MAKER_TOKEN); | ||||
|             const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); | ||||
|             const quotes = await testContract | ||||
|                 .sampleBuysFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) | ||||
|                 .callAsync(); | ||||
|             expect(quotes).to.deep.eq(expectedQuotes); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe('liquidity provider', () => { | ||||
|         const xAsset = randomAddress(); | ||||
|         const yAsset = randomAddress(); | ||||
|   | ||||
| @@ -152,7 +152,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return ['0x', '0x', expectedMakerFillAmounts]; | ||||
|                     return ['0x', '0x', [], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -164,7 +164,7 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [results] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getKyberSellQuotes( | ||||
|                     { hintHandler: randomAddress(), networkProxy: randomAddress(), weth: randomAddress() }, | ||||
|                     new BigNumber(0), | ||||
| @@ -173,7 +173,7 @@ describe('DexSampler tests', () => { | ||||
|                     expectedTakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|             expect(results.samples).to.deep.eq(expectedMakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('getLiquidityProviderSellQuotes()', async () => { | ||||
| @@ -186,7 +186,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(providerAddress).to.eq(poolAddress); | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     return [toBaseUnitAmount(1001)]; | ||||
|                     return [[new BigNumber(gasCost)], [toBaseUnitAmount(1001)]]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -215,6 +215,7 @@ describe('DexSampler tests', () => { | ||||
|                         output: toBaseUnitAmount(1001), | ||||
|                         input: toBaseUnitAmount(1000), | ||||
|                         fillData: { poolAddress, gasCost }, | ||||
|                         gasUsed: new BigNumber(gasCost), | ||||
|                     }, | ||||
|                 ], | ||||
|             ]); | ||||
| @@ -230,7 +231,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(providerAddress).to.eq(poolAddress); | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     return [toBaseUnitAmount(999)]; | ||||
|                     return [[new BigNumber(gasCost)], [toBaseUnitAmount(999)]]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -259,6 +260,7 @@ describe('DexSampler tests', () => { | ||||
|                         output: toBaseUnitAmount(999), | ||||
|                         input: toBaseUnitAmount(1000), | ||||
|                         fillData: { poolAddress, gasCost }, | ||||
|                         gasUsed: new BigNumber(gasCost), | ||||
|                     }, | ||||
|                 ], | ||||
|             ]); | ||||
| @@ -274,7 +276,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return expectedMakerFillAmounts; | ||||
|                     return [[], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -286,7 +288,7 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [result] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getEth2DaiSellQuotes( | ||||
|                     randomAddress(), | ||||
|                     expectedMakerToken, | ||||
| @@ -294,7 +296,7 @@ describe('DexSampler tests', () => { | ||||
|                     expectedTakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|             expect(result.samples).to.deep.eq(expectedMakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('getUniswapSellQuotes()', async () => { | ||||
| @@ -307,7 +309,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return expectedMakerFillAmounts; | ||||
|                     return [[], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -319,7 +321,7 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [results] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getUniswapSellQuotes( | ||||
|                     randomAddress(), | ||||
|                     expectedMakerToken, | ||||
| @@ -327,7 +329,7 @@ describe('DexSampler tests', () => { | ||||
|                     expectedTakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|             expect(results.samples).to.deep.eq(expectedMakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('getUniswapV2SellQuotes()', async () => { | ||||
| @@ -339,7 +341,7 @@ describe('DexSampler tests', () => { | ||||
|                 sampleSellsFromUniswapV2: (_router, path, fillAmounts) => { | ||||
|                     expect(path).to.deep.eq([expectedMakerToken, expectedTakerToken]); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return expectedMakerFillAmounts; | ||||
|                     return [[], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -351,14 +353,14 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [results] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getUniswapV2SellQuotes( | ||||
|                     NULL_ADDRESS, | ||||
|                     [expectedMakerToken, expectedTakerToken], | ||||
|                     expectedTakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|             expect(results.samples).to.deep.eq(expectedMakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('getEth2DaiBuyQuotes()', async () => { | ||||
| @@ -371,7 +373,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|                     return expectedTakerFillAmounts; | ||||
|                     return [[], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -383,7 +385,7 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [results] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getEth2DaiBuyQuotes( | ||||
|                     randomAddress(), | ||||
|                     expectedMakerToken, | ||||
| @@ -391,7 +393,7 @@ describe('DexSampler tests', () => { | ||||
|                     expectedMakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|             expect(results.samples).to.deep.eq(expectedTakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         it('getUniswapBuyQuotes()', async () => { | ||||
| @@ -404,7 +406,7 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|                     return expectedTakerFillAmounts; | ||||
|                     return [[], expectedMakerFillAmounts]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -416,7 +418,7 @@ describe('DexSampler tests', () => { | ||||
|                 undefined, | ||||
|                 async () => undefined, | ||||
|             ); | ||||
|             const [fillableAmounts] = await dexOrderSampler.executeAsync( | ||||
|             const [results] = await dexOrderSampler.executeAsync( | ||||
|                 dexOrderSampler.getUniswapBuyQuotes( | ||||
|                     randomAddress(), | ||||
|                     expectedMakerToken, | ||||
| @@ -424,7 +426,7 @@ describe('DexSampler tests', () => { | ||||
|                     expectedMakerFillAmounts, | ||||
|                 ), | ||||
|             ); | ||||
|             expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|             expect(results.samples).to.deep.eq(expectedTakerFillAmounts); | ||||
|         }); | ||||
|  | ||||
|         interface RatesBySource { | ||||
| @@ -445,20 +447,27 @@ describe('DexSampler tests', () => { | ||||
|             let uniswapRouter: string; | ||||
|             let uniswapV2Router: string; | ||||
|             let eth2DaiRouter: string; | ||||
|             const gasUsed = new BigNumber(123); | ||||
|             const sampler = new MockSamplerContract({ | ||||
|                 sampleSellsFromUniswap: (router, takerToken, makerToken, fillAmounts) => { | ||||
|                     uniswapRouter = router; | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|                 sampleSellsFromEth2Dai: (router, takerToken, makerToken, fillAmounts) => { | ||||
|                     eth2DaiRouter = router; | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|                 sampleSellsFromUniswapV2: (router, path, fillAmounts) => { | ||||
|                     uniswapV2Router = router; | ||||
| @@ -470,7 +479,10 @@ describe('DexSampler tests', () => { | ||||
|                         expect(path).to.have.lengthOf.within(2, 3); | ||||
|                     } | ||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -495,6 +507,7 @@ describe('DexSampler tests', () => { | ||||
|                     source: s, | ||||
|                     input: a, | ||||
|                     output: a.times(ratesBySource[s]).integerValue(), | ||||
|                     gasUsed, | ||||
|                     fillData: (() => { | ||||
|                         if (s === ERC20BridgeSource.UniswapV2) { | ||||
|                             return { | ||||
| @@ -518,6 +531,7 @@ describe('DexSampler tests', () => { | ||||
|                     source: ERC20BridgeSource.UniswapV2, | ||||
|                     input: a, | ||||
|                     output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(), | ||||
|                     gasUsed, | ||||
|                     fillData: { | ||||
|                         router: uniswapV2Router, | ||||
|                         tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken], | ||||
| @@ -539,6 +553,7 @@ describe('DexSampler tests', () => { | ||||
|                 [ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100), | ||||
|             }; | ||||
|             const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); | ||||
|             const gasUsed = new BigNumber(123); | ||||
|             let uniswapRouter: string; | ||||
|             let uniswapV2Router: string; | ||||
|             let eth2DaiRouter: string; | ||||
| @@ -548,14 +563,20 @@ describe('DexSampler tests', () => { | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|                 sampleBuysFromEth2Dai: (router, takerToken, makerToken, fillAmounts) => { | ||||
|                     eth2DaiRouter = router; | ||||
|                     expect(takerToken).to.eq(expectedTakerToken); | ||||
|                     expect(makerToken).to.eq(expectedMakerToken); | ||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|                 sampleBuysFromUniswapV2: (router, path, fillAmounts) => { | ||||
|                     uniswapV2Router = router; | ||||
| @@ -567,7 +588,10 @@ describe('DexSampler tests', () => { | ||||
|                         expect(path).to.have.lengthOf.within(2, 3); | ||||
|                     } | ||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); | ||||
|                     return [ | ||||
|                         fillAmounts.map(_a => gasUsed), | ||||
|                         fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()), | ||||
|                     ]; | ||||
|                 }, | ||||
|             }); | ||||
|             const dexOrderSampler = new DexOrderSampler( | ||||
| @@ -587,6 +611,7 @@ describe('DexSampler tests', () => { | ||||
|                     source: s, | ||||
|                     input: a, | ||||
|                     output: a.times(ratesBySource[s]).integerValue(), | ||||
|                     gasUsed, | ||||
|                     fillData: (() => { | ||||
|                         if (s === ERC20BridgeSource.UniswapV2) { | ||||
|                             return { | ||||
| @@ -609,6 +634,7 @@ describe('DexSampler tests', () => { | ||||
|                     source: ERC20BridgeSource.UniswapV2, | ||||
|                     input: a, | ||||
|                     output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(), | ||||
|                     gasUsed, | ||||
|                     fillData: { | ||||
|                         router: uniswapV2Router, | ||||
|                         tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken], | ||||
|   | ||||
| @@ -102,6 +102,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => { | ||||
|             takerAmount: order.takerAmount, | ||||
|             fills: [], | ||||
|             ...optimizerFields, | ||||
|             gasUsed: new BigNumber(1), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import { NativeOrderWithFillableAmounts } from '../src/types'; | ||||
| import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; | ||||
| import { | ||||
|     BUY_SOURCE_FILTER_BY_CHAIN_ID, | ||||
|     DEFAULT_GET_MARKET_ORDERS_OPTS, | ||||
|     POSITIVE_INF, | ||||
|     SELL_SOURCE_FILTER_BY_CHAIN_ID, | ||||
|     SOURCE_FLAGS, | ||||
| @@ -62,6 +63,7 @@ const SELL_SOURCES = SELL_SOURCE_FILTER_BY_CHAIN_ID[ChainId.Mainnet].sources; | ||||
| const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] }; | ||||
|  | ||||
| const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign }; | ||||
| const GAS_PRICE = new BigNumber(1); | ||||
|  | ||||
| /** | ||||
|  * gets the orders required for a market sell operation by (potentially) merging native orders with | ||||
| @@ -75,7 +77,7 @@ async function getMarketSellOrdersAsync( | ||||
|     utils: MarketOperationUtils, | ||||
|     nativeOrders: SignedNativeOrder[], | ||||
|     takerAmount: BigNumber, | ||||
|     opts?: Partial<GetMarketOrdersOpts>, | ||||
|     opts: GetMarketOrdersOpts, | ||||
| ): Promise<OptimizerResultWithReport> { | ||||
|     return utils.getOptimizerResultAsync(nativeOrders, takerAmount, MarketOperation.Sell, opts); | ||||
| } | ||||
| @@ -92,7 +94,7 @@ async function getMarketBuyOrdersAsync( | ||||
|     utils: MarketOperationUtils, | ||||
|     nativeOrders: SignedNativeOrder[], | ||||
|     makerAmount: BigNumber, | ||||
|     opts?: Partial<GetMarketOrdersOpts>, | ||||
|     opts: GetMarketOrdersOpts, | ||||
| ): Promise<OptimizerResultWithReport> { | ||||
|     return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts); | ||||
| } | ||||
| @@ -129,6 +131,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|     const contractAddresses = { | ||||
|         ...getContractAddressesForChainOrThrow(CHAIN_ID), | ||||
|     }; | ||||
|     let DEFAULT_DEX_GAS_USED = new BigNumber(100000); | ||||
|  | ||||
|     function getMockedQuoteRequestor( | ||||
|         type: 'indicative' | 'firm', | ||||
| @@ -205,6 +208,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|         inputs: Numberish[], | ||||
|         rates: Numberish[], | ||||
|         fillData?: FillData, | ||||
|         gasUsed?: BigNumber, | ||||
|     ): DexSample[] { | ||||
|         const samples: DexSample[] = []; | ||||
|         inputs.forEach((input, i) => { | ||||
| @@ -218,6 +222,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     .times(rate) | ||||
|                     .plus(i === 0 ? 0 : samples[i - 1].output) | ||||
|                     .integerValue(), | ||||
|                 gasUsed: gasUsed || DEFAULT_DEX_GAS_USED, | ||||
|             }); | ||||
|         }); | ||||
|         return samples; | ||||
| @@ -233,7 +238,10 @@ describe('MarketOperationUtils tests', () => { | ||||
|         liquidityProviderAddress?: string, | ||||
|     ) => DexSample[][]; | ||||
|  | ||||
|     function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { | ||||
|     function createGetMultipleSellQuotesOperationFromRates( | ||||
|         rates: RatesBySource, | ||||
|         gasSchedule: Partial<{ [key in ERC20BridgeSource]: BigNumber }> = {}, | ||||
|     ): GetMultipleQuotesOperation { | ||||
|         return ( | ||||
|             sources: ERC20BridgeSource[], | ||||
|             _makerToken: string, | ||||
| @@ -241,11 +249,16 @@ describe('MarketOperationUtils tests', () => { | ||||
|             fillAmounts: BigNumber[], | ||||
|             _wethAddress: string, | ||||
|         ) => { | ||||
|             return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s => createSamplesFromRates(s, fillAmounts, rates[s])); | ||||
|             return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s => | ||||
|                 createSamplesFromRates(s, fillAmounts, rates[s], undefined, gasSchedule[s]), | ||||
|             ); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { | ||||
|     function createGetMultipleBuyQuotesOperationFromRates( | ||||
|         rates: RatesBySource, | ||||
|         gasSchedule: Partial<{ [key in ERC20BridgeSource]: BigNumber }> = {}, | ||||
|     ): GetMultipleQuotesOperation { | ||||
|         return ( | ||||
|             sources: ERC20BridgeSource[], | ||||
|             _makerToken: string, | ||||
| @@ -258,6 +271,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     s, | ||||
|                     fillAmounts, | ||||
|                     rates[s].map(r => new BigNumber(1).div(r)), | ||||
|                     gasSchedule[s], | ||||
|                 ), | ||||
|             ); | ||||
|         }; | ||||
| @@ -334,8 +348,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 poolAddress: randomAddress(), | ||||
|                 tokens: [TAKER_TOKEN, MAKER_TOKEN], | ||||
|                 exchangeFunctionSelector: hexUtils.random(4), | ||||
|                 sellQuoteFunctionSelector: hexUtils.random(4), | ||||
|                 buyQuoteFunctionSelector: hexUtils.random(4), | ||||
|             }, | ||||
|             fromTokenIdx: 0, | ||||
|             toTokenIdx: 1, | ||||
| @@ -345,8 +357,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 poolAddress: randomAddress(), | ||||
|                 tokens: [TAKER_TOKEN, MAKER_TOKEN], | ||||
|                 exchangeFunctionSelector: hexUtils.random(4), | ||||
|                 sellQuoteFunctionSelector: hexUtils.random(4), | ||||
|                 buyQuoteFunctionSelector: hexUtils.random(4), | ||||
|             }, | ||||
|             fromTokenIdx: 0, | ||||
|             toTokenIdx: 1, | ||||
| @@ -356,8 +366,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 poolAddress: randomAddress(), | ||||
|                 tokens: [TAKER_TOKEN, MAKER_TOKEN], | ||||
|                 exchangeFunctionSelector: hexUtils.random(4), | ||||
|                 sellQuoteFunctionSelector: hexUtils.random(4), | ||||
|                 buyQuoteFunctionSelector: hexUtils.random(4), | ||||
|             }, | ||||
|             fromTokenIdx: 0, | ||||
|             toTokenIdx: 1, | ||||
| @@ -367,8 +375,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 poolAddress: randomAddress(), | ||||
|                 tokens: [TAKER_TOKEN, MAKER_TOKEN], | ||||
|                 exchangeFunctionSelector: hexUtils.random(4), | ||||
|                 sellQuoteFunctionSelector: hexUtils.random(4), | ||||
|                 buyQuoteFunctionSelector: hexUtils.random(4), | ||||
|             }, | ||||
|             fromTokenIdx: 0, | ||||
|             toTokenIdx: 1, | ||||
| @@ -378,8 +384,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 poolAddress: randomAddress(), | ||||
|                 tokens: [TAKER_TOKEN, MAKER_TOKEN], | ||||
|                 exchangeFunctionSelector: hexUtils.random(4), | ||||
|                 sellQuoteFunctionSelector: hexUtils.random(4), | ||||
|                 buyQuoteFunctionSelector: hexUtils.random(4), | ||||
|             }, | ||||
|             fromTokenIdx: 0, | ||||
|             toTokenIdx: 1, | ||||
| @@ -455,15 +459,15 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 FILL_AMOUNT, | ||||
|                 _.times(NUM_SAMPLES, i => DEFAULT_RATES[ERC20BridgeSource.Native][i]), | ||||
|             ); | ||||
|             const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = { | ||||
|             const DEFAULT_OPTS: GetMarketOrdersOpts = { | ||||
|                 ...DEFAULT_GET_MARKET_ORDERS_OPTS, | ||||
|                 numSamples: NUM_SAMPLES, | ||||
|                 sampleDistributionBase: 1, | ||||
|                 bridgeSlippage: 0, | ||||
|                 maxFallbackSlippage: 100, | ||||
|                 excludedSources: DEFAULT_EXCLUDED, | ||||
|                 allowFallback: false, | ||||
|                 gasSchedule: {}, | ||||
|                 feeSchedule: {}, | ||||
|                 gasPrice: GAS_PRICE, | ||||
|             }; | ||||
|  | ||||
|             beforeEach(() => { | ||||
| @@ -641,10 +645,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|  | ||||
|                 let requestedComparisonPrice: BigNumber | undefined; | ||||
|  | ||||
|                 // to get a comparisonPrice, you need a feeschedule for a native order | ||||
|                 const feeSchedule = { | ||||
|                     [ERC20BridgeSource.Native]: _.constant(new BigNumber(1)), | ||||
|                 }; | ||||
|                 mockedQuoteRequestor | ||||
|                     .setup(mqr => mqr.getMakerUriForSignature(TypeMoq.It.isValue(SIGNATURE))) | ||||
|                     .returns(() => 'https://foo.bar'); | ||||
| @@ -703,13 +703,13 @@ describe('MarketOperationUtils tests', () => { | ||||
|                         mou.getMarketSellLiquidityAsync(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), | ||||
|                     ) | ||||
|                     .returns(async () => { | ||||
|                         return { | ||||
|                         const marketSellLiquidity: MarketSideLiquidity = { | ||||
|                             side: MarketOperation.Sell, | ||||
|                             inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18), | ||||
|                             inputToken: MAKER_TOKEN, | ||||
|                             outputToken: TAKER_TOKEN, | ||||
|                             inputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 18), | ||||
|                             outputAmountPerEth: Web3Wrapper.toBaseUnitAmount(1, 6), | ||||
|                             inputAmountPerEth: new BigNumber(1), // selling ETH so 1:1 | ||||
|                             outputAmountPerEth: new BigNumber(0.00000000241319391), // buying USDC (6 decimal) | ||||
|                             quoteSourceFilters: new SourceFilters(), | ||||
|                             makerTokenDecimals: 6, | ||||
|                             takerTokenDecimals: 18, | ||||
| @@ -735,14 +735,15 @@ describe('MarketOperationUtils tests', () => { | ||||
|                             }, | ||||
|                             isRfqSupported: true, | ||||
|                         }; | ||||
|  | ||||
|                         return marketSellLiquidity; | ||||
|                     }); | ||||
|                 const result = await mockedMarketOpUtils.object.getOptimizerResultAsync( | ||||
|                     ORDERS, | ||||
|                     Web3Wrapper.toBaseUnitAmount(1, 18), | ||||
|                     Web3Wrapper.toBaseUnitAmount(10, 18), | ||||
|                     MarketOperation.Sell, | ||||
|                     { | ||||
|                         ...DEFAULT_OPTS, | ||||
|                         feeSchedule, | ||||
|                         rfqt: { | ||||
|                             isIndicative: false, | ||||
|                             apiKey: 'foo', | ||||
| @@ -1066,19 +1067,13 @@ describe('MarketOperationUtils tests', () => { | ||||
|             it('factors in fees for native orders', async () => { | ||||
|                 // Native orders will have the best rates but have fees, | ||||
|                 // dropping their effective rates. | ||||
|                 const nativeFeeRate = 0.06; | ||||
|                 const gasPrice = new BigNumber(1000e9); | ||||
|                 DEFAULT_DEX_GAS_USED = new BigNumber(100000); | ||||
|                 const rates: RatesBySource = { | ||||
|                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, 0.93, 0.92, 0.91] | ||||
|                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], | ||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], | ||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||
|                 }; | ||||
|                 const feeSchedule = { | ||||
|                     [ERC20BridgeSource.Native]: _.constant( | ||||
|                         FILL_AMOUNT.div(4) | ||||
|                             .times(nativeFeeRate) | ||||
|                             .dividedToIntegerBy(ETH_TO_MAKER_RATE), | ||||
|                     ), | ||||
|                     [ERC20BridgeSource.Native]: [1, 0.96, 0.94, 0.92], // Effectively [0.98, 0.94, 0.92, 0.90] | ||||
|                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], // [0.95] | ||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], // [0.94] | ||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], // [0.094] | ||||
|                 }; | ||||
|                 replaceSamplerOps({ | ||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), | ||||
| @@ -1088,7 +1083,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     marketOperationUtils, | ||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||
|                     FILL_AMOUNT, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, gasPrice }, | ||||
|                 ); | ||||
|                 const improvedOrders = improvedOrdersResponse.optimizedOrders; | ||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||
| @@ -1104,7 +1099,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|             it('factors in fees for dexes', async () => { | ||||
|                 // Kyber will have the best rates but will have fees, | ||||
|                 // dropping its effective rates. | ||||
|                 const uniswapFeeRate = 0.2; | ||||
|                 const rates: RatesBySource = { | ||||
|                     [ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1], | ||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||
| @@ -1112,13 +1106,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     // Effectively [0.8, ~0.5, ~0, ~0] | ||||
|                     [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], | ||||
|                 }; | ||||
|                 const feeSchedule = { | ||||
|                     [ERC20BridgeSource.Uniswap]: _.constant( | ||||
|                         FILL_AMOUNT.div(4) | ||||
|                             .times(uniswapFeeRate) | ||||
|                             .dividedToIntegerBy(ETH_TO_MAKER_RATE), | ||||
|                     ), | ||||
|                 }; | ||||
|                 replaceSamplerOps({ | ||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), | ||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), | ||||
| @@ -1127,7 +1114,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     marketOperationUtils, | ||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||
|                     FILL_AMOUNT, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4 }, | ||||
|                 ); | ||||
|                 const improvedOrders = improvedOrdersResponse.optimizedOrders; | ||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||
| @@ -1240,10 +1227,12 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     FILL_AMOUNT, | ||||
|                     MarketOperation.Sell, | ||||
|                     { | ||||
|                         ...DEFAULT_GET_MARKET_ORDERS_OPTS, | ||||
|                         includedSources: [ERC20BridgeSource.LiquidityProvider], | ||||
|                         excludedSources: [], | ||||
|                         numSamples: 4, | ||||
|                         bridgeSlippage: 0, | ||||
|                         gasPrice: GAS_PRICE, | ||||
|                     }, | ||||
|                 ); | ||||
|                 const result = ordersAndReport.optimizedOrders; | ||||
| @@ -1313,15 +1302,14 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 FILL_AMOUNT, | ||||
|                 _.times(NUM_SAMPLES, () => DEFAULT_RATES[ERC20BridgeSource.Native][0]), | ||||
|             ); | ||||
|             const DEFAULT_OPTS: Partial<GetMarketOrdersOpts> = { | ||||
|             const DEFAULT_OPTS: GetMarketOrdersOpts = { | ||||
|                 ...DEFAULT_GET_MARKET_ORDERS_OPTS, | ||||
|                 numSamples: NUM_SAMPLES, | ||||
|                 sampleDistributionBase: 1, | ||||
|                 bridgeSlippage: 0, | ||||
|                 maxFallbackSlippage: 100, | ||||
|                 excludedSources: DEFAULT_EXCLUDED, | ||||
|                 allowFallback: false, | ||||
|                 gasSchedule: {}, | ||||
|                 feeSchedule: {}, | ||||
|             }; | ||||
|  | ||||
|             beforeEach(() => { | ||||
| @@ -1528,20 +1516,13 @@ describe('MarketOperationUtils tests', () => { | ||||
|             it('factors in fees for native orders', async () => { | ||||
|                 // Native orders will have the best rates but have fees, | ||||
|                 // dropping their effective rates. | ||||
|                 const nativeFeeRate = 0.06; | ||||
|                 const gasPrice = new BigNumber(1000e9); | ||||
|                 DEFAULT_DEX_GAS_USED = new BigNumber(100000); | ||||
|                 const rates: RatesBySource = { | ||||
|                     ...ZERO_RATES, | ||||
|                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91] | ||||
|                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], | ||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], | ||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||
|                 }; | ||||
|                 const feeSchedule = { | ||||
|                     [ERC20BridgeSource.Native]: _.constant( | ||||
|                         FILL_AMOUNT.div(4) | ||||
|                             .times(nativeFeeRate) | ||||
|                             .dividedToIntegerBy(ETH_TO_TAKER_RATE), | ||||
|                     ), | ||||
|                     [ERC20BridgeSource.Native]: [1, 0.96, 0.94, 0.92], // Effectively [0.98, 0.94, 0.92, 0.90] | ||||
|                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], // [0.95] | ||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], // [0.94] | ||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], // [0.094] | ||||
|                 }; | ||||
|                 replaceSamplerOps({ | ||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||
| @@ -1551,7 +1532,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     marketOperationUtils, | ||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||
|                     FILL_AMOUNT, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, gasPrice }, | ||||
|                 ); | ||||
|                 const improvedOrders = improvedOrdersResponse.optimizedOrders; | ||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||
| @@ -1567,7 +1548,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|             it('factors in fees for dexes', async () => { | ||||
|                 // Uniswap will have the best rates but will have fees, | ||||
|                 // dropping its effective rates. | ||||
|                 const uniswapFeeRate = 0.2; | ||||
|                 const rates: RatesBySource = { | ||||
|                     ...ZERO_RATES, | ||||
|                     [ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1], | ||||
| @@ -1575,13 +1555,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], | ||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], | ||||
|                 }; | ||||
|                 const feeSchedule = { | ||||
|                     [ERC20BridgeSource.Uniswap]: _.constant( | ||||
|                         FILL_AMOUNT.div(4) | ||||
|                             .times(uniswapFeeRate) | ||||
|                             .dividedToIntegerBy(ETH_TO_TAKER_RATE), | ||||
|                     ), | ||||
|                 }; | ||||
|                 replaceSamplerOps({ | ||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE), | ||||
| @@ -1590,7 +1563,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                     marketOperationUtils, | ||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||
|                     FILL_AMOUNT, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, | ||||
|                     { ...DEFAULT_OPTS, numSamples: 4 }, | ||||
|                 ); | ||||
|                 const improvedOrders = improvedOrdersResponse.optimizedOrders; | ||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||
| @@ -1726,9 +1699,6 @@ describe('MarketOperationUtils tests', () => { | ||||
|             signature: SIGNATURE, | ||||
|         }; | ||||
|         const orders = [smallOrder, largeOrder]; | ||||
|         const feeSchedule = { | ||||
|             [ERC20BridgeSource.Native]: _.constant(2e5), | ||||
|         }; | ||||
|  | ||||
|         it('penalizes native fill based on target amount when target is smaller', () => { | ||||
|             const path = createFills({ | ||||
| @@ -1737,7 +1707,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 dexQuotes: [], | ||||
|                 targetInput: takerAmount.minus(1), | ||||
|                 outputAmountPerEth, | ||||
|                 feeSchedule, | ||||
|                 gasPrice: GAS_PRICE, | ||||
|             }); | ||||
|             expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker); | ||||
|             expect(path[0][0].input).to.be.bignumber.eq(takerAmount.minus(1)); | ||||
| @@ -1750,7 +1720,7 @@ describe('MarketOperationUtils tests', () => { | ||||
|                 dexQuotes: [], | ||||
|                 targetInput: POSITIVE_INF, | ||||
|                 outputAmountPerEth, | ||||
|                 feeSchedule, | ||||
|                 gasPrice: GAS_PRICE, | ||||
|             }); | ||||
|             expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker); | ||||
|             expect((path[0][1].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import 'mocha'; | ||||
| import * as TypeMoq from 'typemoq'; | ||||
|  | ||||
| import { MarketOperation, NativeOrderWithFillableAmounts } from '../src/types'; | ||||
| import { NATIVE_LIMIT_ORDER_GAS_USED, NATIVE_RFQT_GAS_USED } from '../src/utils/market_operation_utils/constants'; | ||||
| import { | ||||
|     CollapsedFill, | ||||
|     DexSample, | ||||
| @@ -34,6 +35,9 @@ import { getRandomAmount, getRandomSignature } from './utils/utils'; | ||||
| chaiSetup.configure(); | ||||
| const expect = chai.expect; | ||||
|  | ||||
| const ONE = new BigNumber(1); | ||||
| const GAS_USED = ONE; | ||||
|  | ||||
| function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): NativeCollapsedFill { | ||||
|     const fillData = { | ||||
|         order: order.order, | ||||
| @@ -51,6 +55,8 @@ function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): Na | ||||
|                 ? (fillData as NativeLimitOrderFillData) | ||||
|                 : (fillData as NativeRfqOrderFillData), | ||||
|         subFills: [], | ||||
|         gasUsed: | ||||
|             order.type === FillQuoteTransformerOrderType.Limit ? NATIVE_LIMIT_ORDER_GAS_USED : NATIVE_RFQT_GAS_USED, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -63,12 +69,14 @@ describe('generateQuoteReport', async () => { | ||||
|             input: new BigNumber(10003), | ||||
|             output: new BigNumber(10004), | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const uniswapSample2: DexSample = { | ||||
|             source: ERC20BridgeSource.UniswapV2, | ||||
|             input: new BigNumber(10005), | ||||
|             output: new BigNumber(10006), | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const orderbookOrder1: NativeOrderWithFillableAmounts = { | ||||
|             order: new LimitOrder({ takerAmount: new BigNumber(1000) }), | ||||
| @@ -161,6 +169,7 @@ describe('generateQuoteReport', async () => { | ||||
|             fillData: { | ||||
|                 order: rfqtOrder1.order, | ||||
|             } as NativeRfqOrderFillData, | ||||
|             gasUsed: NATIVE_RFQT_GAS_USED, | ||||
|         }; | ||||
|         const rfqtOrder2Source: NativeRfqOrderQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.Native, | ||||
| @@ -173,6 +182,7 @@ describe('generateQuoteReport', async () => { | ||||
|             fillData: { | ||||
|                 order: rfqtOrder2.order, | ||||
|             } as NativeRfqOrderFillData, | ||||
|             gasUsed: NATIVE_RFQT_GAS_USED, | ||||
|         }; | ||||
|         const orderbookOrder2Source: NativeLimitOrderQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.Native, | ||||
| @@ -183,18 +193,21 @@ describe('generateQuoteReport', async () => { | ||||
|             fillData: { | ||||
|                 order: orderbookOrder2.order, | ||||
|             } as NativeLimitOrderFillData, | ||||
|             gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|         }; | ||||
|         const uniswap2Source: BridgeQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.UniswapV2, | ||||
|             makerAmount: uniswapSample2.output, | ||||
|             takerAmount: uniswapSample2.input, | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const kyber2Source: BridgeQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.Kyber, | ||||
|             makerAmount: kyberSample2.output, | ||||
|             takerAmount: kyberSample2.input, | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|  | ||||
|         const expectedSourcesConsidered: QuoteReportEntry[] = [rfqtOrder1Source, rfqtOrder2Source]; | ||||
| @@ -215,12 +228,14 @@ describe('generateQuoteReport', async () => { | ||||
|             input: new BigNumber(10000), | ||||
|             output: new BigNumber(10001), | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const uniswapSample1: DexSample = { | ||||
|             source: ERC20BridgeSource.UniswapV2, | ||||
|             input: new BigNumber(10003), | ||||
|             output: new BigNumber(10004), | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const orderbookOrder1: NativeOrderWithFillableAmounts = { | ||||
|             order: new LimitOrder({ takerAmount: new BigNumber(1101) }), | ||||
| @@ -267,18 +282,21 @@ describe('generateQuoteReport', async () => { | ||||
|             fillData: { | ||||
|                 order: orderbookOrder1.order, | ||||
|             } as NativeLimitOrderFillData, | ||||
|             gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|         }; | ||||
|         const uniswap1Source: BridgeQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.UniswapV2, | ||||
|             makerAmount: uniswapSample1.input, | ||||
|             takerAmount: uniswapSample1.output, | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|         const kyber1Source: BridgeQuoteReportEntry = { | ||||
|             liquiditySource: ERC20BridgeSource.Kyber, | ||||
|             makerAmount: kyberSample1.input, | ||||
|             takerAmount: kyberSample1.output, | ||||
|             fillData: {}, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|  | ||||
|         // No order is considered here because only Native RFQ orders are considered. | ||||
| @@ -303,15 +321,15 @@ describe('generateQuoteReport', async () => { | ||||
|                 source: ERC20BridgeSource.Balancer, | ||||
|                 fillData: {}, | ||||
|                 encodeCall: () => '', | ||||
|                 handleCallResults: _callResults => [new BigNumber(1337)], | ||||
|                 handleRevert: _c => [], | ||||
|                 handleCallResults: _callResults => ({ gasUsed: [], samples: [new BigNumber(1337)] }), | ||||
|                 handleRevert: _c => ({ gasUsed: [], samples: [] }), | ||||
|             }, | ||||
|             secondHopSource: { | ||||
|                 source: ERC20BridgeSource.Curve, | ||||
|                 fillData: {}, | ||||
|                 encodeCall: () => '', | ||||
|                 handleCallResults: _callResults => [new BigNumber(1337)], | ||||
|                 handleRevert: _c => [], | ||||
|                 handleCallResults: _callResults => ({ gasUsed: [], samples: [new BigNumber(1337)] }), | ||||
|                 handleRevert: _c => ({ gasUsed: [], samples: [] }), | ||||
|             }, | ||||
|         }; | ||||
|         const twoHopSample: DexSample<MultiHopFillData> = { | ||||
| @@ -319,6 +337,7 @@ describe('generateQuoteReport', async () => { | ||||
|             input: new BigNumber(3005), | ||||
|             output: new BigNumber(3006), | ||||
|             fillData: twoHopFillData, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|  | ||||
|         const orderReport = generateQuoteReport(marketOperation, [orderbookOrder1], twoHopSample); | ||||
| @@ -328,6 +347,7 @@ describe('generateQuoteReport', async () => { | ||||
|             takerAmount: twoHopSample.input, | ||||
|             hopSources: [ERC20BridgeSource.Balancer, ERC20BridgeSource.Curve], | ||||
|             fillData: twoHopFillData, | ||||
|             gasUsed: GAS_USED, | ||||
|         }; | ||||
|  | ||||
|         // No entry is present in considered because No RFQ orders were reported. | ||||
| @@ -356,12 +376,12 @@ function expectEqualQuoteReportEntries( | ||||
|             expect(actualEntry.fillData.order).to.eql( | ||||
|                 // tslint:disable-next-line:no-unnecessary-type-assertion | ||||
|                 (expectedEntry.fillData as NativeFillData).order, | ||||
|                 `${variableName} incorrect at index ${idx}`, | ||||
|                 `${actualEntry.liquiditySource} ${variableName} incorrect at index ${idx}`, | ||||
|             ); | ||||
|         } | ||||
|         expect(_.omit(actualEntry, 'fillData')).to.eql( | ||||
|             _.omit(expectedEntry, 'fillData'), | ||||
|             `${variableName} incorrect at index ${idx}`, | ||||
|             `${actualEntry.liquiditySource} ${variableName} incorrect at index ${idx}`, | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils'; | ||||
| import * as _ from 'lodash'; | ||||
|  | ||||
| import { MarketOperation } from '../src/types'; | ||||
| import { NATIVE_LIMIT_ORDER_GAS_USED } from '../src/utils/market_operation_utils/constants'; | ||||
| import { | ||||
|     CollapsedFill, | ||||
|     ERC20BridgeSource, | ||||
| @@ -26,8 +27,6 @@ describe('quote_simulation tests', async () => { | ||||
|     const ONE = new BigNumber(1); | ||||
|     const MAKER_TOKEN = randomAddress(); | ||||
|     const TAKER_TOKEN = randomAddress(); | ||||
|     const GAS_SCHEDULE = { [ERC20BridgeSource.Uniswap]: _.constant(1), [ERC20BridgeSource.Native]: _.constant(1) }; | ||||
|  | ||||
|     // Check if two numbers are within `maxError` error rate within each other. | ||||
|     function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-10): void { | ||||
|         // |n2-n1| / max(|n1|, |n2|) | ||||
| @@ -161,6 +160,7 @@ describe('quote_simulation tests', async () => { | ||||
|             }, | ||||
|             type, | ||||
|             fills: createOrderCollapsedFills(fillableInput, fillableOutput, fillsCount), | ||||
|             gasUsed: NATIVE_LIMIT_ORDER_GAS_USED, | ||||
|         }; | ||||
|         return order; | ||||
|     } | ||||
| @@ -182,6 +182,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     input: subFillInputs[j], | ||||
|                     output: subFillOutputs[j], | ||||
|                 })), | ||||
|                 gasUsed: new BigNumber(1), | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| @@ -247,7 +248,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     fillsCount, | ||||
|                     count: 1, | ||||
|                 }); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(fillableInput); | ||||
| @@ -269,7 +270,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     count: 1, | ||||
|                 }); | ||||
|                 const inputFillAmount = fillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(inputFillAmount); | ||||
| @@ -295,7 +296,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     count: 1, | ||||
|                 }); | ||||
|                 const inputFillAmount = fillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(inputFillAmount); | ||||
| @@ -318,7 +319,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     count: 1, | ||||
|                 }); | ||||
|                 const inputFillAmount = fillableInput.times(3 / 2).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(fillableInput); | ||||
| @@ -343,7 +344,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 }); | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, totalFillableInput); | ||||
| @@ -370,7 +371,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = totalFillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, inputFillAmount); | ||||
| @@ -397,7 +398,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = totalFillableInput.times(3 / 2).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, totalFillableInput); | ||||
| @@ -423,7 +424,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 }); | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, fillableInput); | ||||
| @@ -450,7 +451,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = fillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, inputFillAmount); | ||||
| @@ -477,7 +478,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = fillableInput.times(3 / 2).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, fillableInput); | ||||
| @@ -500,7 +501,7 @@ describe('quote_simulation tests', async () => { | ||||
|                     count: 1, | ||||
|                     type: FillQuoteTransformerOrderType.Rfq, | ||||
|                 }); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(fillableInput); | ||||
| @@ -516,7 +517,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const fillableInput = getRandomOrderSize(); | ||||
|                 const fillableOutput = getRandomOrderSize(); | ||||
|                 const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side }); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(fillableInput); | ||||
| @@ -531,7 +532,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const fillableOutput = getRandomOrderSize(); | ||||
|                 const inputFillAmount = fillableInput.times(2 / 3).integerValue(); | ||||
|                 const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side }); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(inputFillAmount); | ||||
| @@ -545,7 +546,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const fillableOutput = getRandomOrderSize(); | ||||
|                 const inputFillAmount = fillableInput.times(3 / 2).integerValue(); | ||||
|                 const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side }); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 expect(totalFilledInput).to.bignumber.eq(fillableInput); | ||||
| @@ -567,7 +568,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 }); | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, totalFillableInput); | ||||
| @@ -591,7 +592,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = totalFillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, inputFillAmount); | ||||
| @@ -615,7 +616,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate; | ||||
|                 const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = totalFillableInput.times(3 / 2).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, totalFillableInput); | ||||
| @@ -638,7 +639,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 }); | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, fillableInput, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, fillableInput); | ||||
| @@ -662,7 +663,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = fillableInput.times(2 / 3).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, inputFillAmount); | ||||
| @@ -686,7 +687,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate; | ||||
|                 const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue(); | ||||
|                 const inputFillAmount = fillableInput.times(3 / 2).integerValue(); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE); | ||||
|                 const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE); | ||||
|                 const totalFilledInput = result.input.plus(result.inputFee); | ||||
|                 const totalFilledOutput = result.output.plus(result.outputFee); | ||||
|                 assertRoughlyEquals(totalFilledInput, fillableInput); | ||||
| @@ -744,7 +745,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             if (side === MarketOperation.Sell) { | ||||
|                 expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableOutput); | ||||
| @@ -769,7 +770,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             expect(result.gas).to.eq(countCollapsedFills(orders)); | ||||
|             expect(result.protocolFeeAmount).to.bignumber.gt(orders.length); | ||||
| @@ -801,7 +802,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: inputFillAmount, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             expect(result.gas).to.gt(0); | ||||
|             expect(result.protocolFeeAmount).to.bignumber.gt(0); | ||||
| @@ -835,7 +836,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: totalFillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|  | ||||
|             assertRoughlyEquals(result.takerAssetAmount, fillableInput); | ||||
| @@ -865,7 +866,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: inputFillAmount, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             expect(result.gas).to.gt(0); | ||||
|             expect(result.protocolFeeAmount).to.bignumber.gt(0); | ||||
| @@ -893,7 +894,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             expect(result.gas).to.eq(countCollapsedFills(orders)); | ||||
|             expect(result.protocolFeeAmount).to.bignumber.gt(orders.length); | ||||
| @@ -923,7 +924,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: inputFillAmount, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             expect(result.gas).to.gt(0); | ||||
|             expect(result.protocolFeeAmount).to.bignumber.gt(0); | ||||
| @@ -951,7 +952,7 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE, slippage }, | ||||
|                 opts: { slippage }, | ||||
|             }); | ||||
|             if (side === MarketOperation.Sell) { | ||||
|                 const slippedOutput = fillableOutput.times(1 - slippage).integerValue(); | ||||
| @@ -982,14 +983,14 @@ describe('quote_simulation tests', async () => { | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE }, | ||||
|                 opts: {}, | ||||
|             }); | ||||
|             const worstCase = simulateWorstCaseFill({ | ||||
|                 orders, | ||||
|                 side, | ||||
|                 fillAmount: fillableInput, | ||||
|                 gasPrice: ONE, | ||||
|                 opts: { gasSchedule: GAS_SCHEDULE, slippage: orderSlippage }, | ||||
|                 opts: { slippage: orderSlippage }, | ||||
|             }); | ||||
|             const bestPrice = bestCase.makerAssetAmount.div(bestCase.totalTakerAssetAmount); | ||||
|             const worstPrice = worstCase.makerAssetAmount.div(worstCase.totalTakerAssetAmount); | ||||
|   | ||||
| @@ -14,7 +14,7 @@ export type GetOrderFillableAssetAmountHandler = ( | ||||
|     devUtilsAddress: string, | ||||
| ) => GetOrderFillableAssetAmountResult; | ||||
|  | ||||
| export type SampleResults = BigNumber[]; | ||||
| export type SampleResults = [BigNumber[], BigNumber[]]; | ||||
| export type SampleSellsUniswapHandler = ( | ||||
|     router: string, | ||||
|     takerToken: string, | ||||
| @@ -44,22 +44,22 @@ export type SampleSellsKyberHandler = ( | ||||
|     takerToken: string, | ||||
|     makerToken: string, | ||||
|     takerTokenAmounts: BigNumber[], | ||||
| ) => [string, string, SampleResults]; | ||||
| ) => [string, string, BigNumber[], BigNumber[]]; | ||||
| export type SampleBuysKyberHandler = ( | ||||
|     reserveId: string, | ||||
|     takerToken: string, | ||||
|     makerToken: string, | ||||
|     makerTokenAmounts: BigNumber[], | ||||
| ) => [string, SampleResults]; | ||||
| ) => [string, string, BigNumber[], BigNumber[]]; | ||||
| export type SampleUniswapV2Handler = (router: string, path: string[], assetAmounts: BigNumber[]) => SampleResults; | ||||
| export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults; | ||||
| export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => BigNumber[]; | ||||
| export type SampleSellsLPHandler = ( | ||||
|     providerAddress: string, | ||||
|     takerToken: string, | ||||
|     makerToken: string, | ||||
|     takerTokenAmounts: BigNumber[], | ||||
| ) => SampleResults; | ||||
| export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults; | ||||
| export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => BigNumber[]; | ||||
|  | ||||
| const DUMMY_PROVIDER = { | ||||
|     sendAsync: (..._args: any[]): any => { | ||||
| @@ -130,7 +130,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         takerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<[string, string, BigNumber[]]> { | ||||
|     ): ContractTxFunctionObj<[string, string, BigNumber[], BigNumber[]]> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleSellsFromKyberNetwork, | ||||
|             this._handlers.sampleSellsFromKyberNetwork, | ||||
| @@ -146,7 +146,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         takerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleSellsFromEth2Dai, | ||||
|             this._handlers.sampleSellsFromEth2Dai, | ||||
| @@ -162,7 +162,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         takerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleSellsFromUniswap, | ||||
|             this._handlers.sampleSellsFromUniswap, | ||||
| @@ -177,7 +177,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         router: string, | ||||
|         path: string[], | ||||
|         takerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleSellsFromUniswapV2, | ||||
|             this._handlers.sampleSellsFromUniswapV2, | ||||
| @@ -192,7 +192,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         takerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleSellsFromLiquidityProvider, | ||||
|             this._handlers.sampleSellsFromLiquidityProvider, | ||||
| @@ -208,7 +208,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         makerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleBuysFromEth2Dai, | ||||
|             this._handlers.sampleBuysFromEth2Dai, | ||||
| @@ -224,7 +224,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         takerToken: string, | ||||
|         makerToken: string, | ||||
|         makerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleBuysFromUniswap, | ||||
|             this._handlers.sampleBuysFromUniswap, | ||||
| @@ -239,7 +239,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { | ||||
|         router: string, | ||||
|         path: string[], | ||||
|         makerAssetAmounts: BigNumber[], | ||||
|     ): ContractTxFunctionObj<BigNumber[]> { | ||||
|     ): ContractTxFunctionObj<SampleResults> { | ||||
|         return this._wrapCall( | ||||
|             super.sampleBuysFromUniswapV2, | ||||
|             this._handlers.sampleBuysFromUniswapV2, | ||||
|   | ||||
| @@ -3,28 +3,23 @@ | ||||
|  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. | ||||
|  * ----------------------------------------------------------------------------- | ||||
|  */ | ||||
| export * from '../test/generated-wrappers/approximate_buys'; | ||||
| export * from '../test/generated-wrappers/balance_checker'; | ||||
| export * from '../test/generated-wrappers/balancer_sampler'; | ||||
| export * from '../test/generated-wrappers/balancer_v2_sampler'; | ||||
| export * from '../test/generated-wrappers/bancor_sampler'; | ||||
| export * from '../test/generated-wrappers/curve_sampler'; | ||||
| export * from '../test/generated-wrappers/curve_v2_sampler'; | ||||
| export * from '../test/generated-wrappers/d_o_d_o_sampler'; | ||||
| export * from '../test/generated-wrappers/d_o_d_o_v2_sampler'; | ||||
| export * from '../test/generated-wrappers/delegate_hacked_erc20'; | ||||
| export * from '../test/generated-wrappers/dummy_liquidity_provider'; | ||||
| export * from '../test/generated-wrappers/erc20_bridge_sampler'; | ||||
| export * from '../test/generated-wrappers/eth2_dai_sampler'; | ||||
| export * from '../test/generated-wrappers/fake_taker'; | ||||
| export * from '../test/generated-wrappers/i_balancer'; | ||||
| export * from '../test/generated-wrappers/i_bancor'; | ||||
| export * from '../test/generated-wrappers/i_curve'; | ||||
| export * from '../test/generated-wrappers/gas_overhead'; | ||||
| export * from '../test/generated-wrappers/hacked_erc20'; | ||||
| export * from '../test/generated-wrappers/i_eth2_dai'; | ||||
| export * from '../test/generated-wrappers/i_kyber_network'; | ||||
| export * from '../test/generated-wrappers/i_m_stable'; | ||||
| export * from '../test/generated-wrappers/i_mooniswap'; | ||||
| export * from '../test/generated-wrappers/i_multi_bridge'; | ||||
| export * from '../test/generated-wrappers/i_shell'; | ||||
| export * from '../test/generated-wrappers/i_smoothy'; | ||||
| export * from '../test/generated-wrappers/i_uniswap_exchange_quotes'; | ||||
| export * from '../test/generated-wrappers/i_uniswap_v2_router01'; | ||||
| export * from '../test/generated-wrappers/kyber_dmm_sampler'; | ||||
| @@ -34,11 +29,9 @@ export * from '../test/generated-wrappers/liquidity_provider_sampler'; | ||||
| export * from '../test/generated-wrappers/m_stable_sampler'; | ||||
| export * from '../test/generated-wrappers/maker_p_s_m_sampler'; | ||||
| export * from '../test/generated-wrappers/mooniswap_sampler'; | ||||
| export * from '../test/generated-wrappers/multi_bridge_sampler'; | ||||
| export * from '../test/generated-wrappers/native_order_sampler'; | ||||
| export * from '../test/generated-wrappers/sampler_utils'; | ||||
| export * from '../test/generated-wrappers/shell_sampler'; | ||||
| export * from '../test/generated-wrappers/smoothy_sampler'; | ||||
| export * from '../test/generated-wrappers/swap_revert_sampler'; | ||||
| export * from '../test/generated-wrappers/test_erc20_bridge_sampler'; | ||||
| export * from '../test/generated-wrappers/test_native_order_sampler'; | ||||
| export * from '../test/generated-wrappers/two_hop_sampler'; | ||||
|   | ||||
| @@ -4,30 +4,28 @@ | ||||
|     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], | ||||
|     "files": [ | ||||
|         "generated-artifacts/BalanceChecker.json", | ||||
|         "generated-artifacts/DelegateHackedERC20.json", | ||||
|         "generated-artifacts/ERC20BridgeSampler.json", | ||||
|         "generated-artifacts/FakeTaker.json", | ||||
|         "test/generated-artifacts/ApproximateBuys.json", | ||||
|         "generated-artifacts/GasOverhead.json", | ||||
|         "generated-artifacts/HackedERC20.json", | ||||
|         "test/generated-artifacts/BalanceChecker.json", | ||||
|         "test/generated-artifacts/BalancerSampler.json", | ||||
|         "test/generated-artifacts/BalancerV2Sampler.json", | ||||
|         "test/generated-artifacts/BancorSampler.json", | ||||
|         "test/generated-artifacts/CurveSampler.json", | ||||
|         "test/generated-artifacts/CurveV2Sampler.json", | ||||
|         "test/generated-artifacts/DODOSampler.json", | ||||
|         "test/generated-artifacts/DODOV2Sampler.json", | ||||
|         "test/generated-artifacts/DelegateHackedERC20.json", | ||||
|         "test/generated-artifacts/DummyLiquidityProvider.json", | ||||
|         "test/generated-artifacts/ERC20BridgeSampler.json", | ||||
|         "test/generated-artifacts/Eth2DaiSampler.json", | ||||
|         "test/generated-artifacts/FakeTaker.json", | ||||
|         "test/generated-artifacts/IBalancer.json", | ||||
|         "test/generated-artifacts/IBancor.json", | ||||
|         "test/generated-artifacts/ICurve.json", | ||||
|         "test/generated-artifacts/GasOverhead.json", | ||||
|         "test/generated-artifacts/HackedERC20.json", | ||||
|         "test/generated-artifacts/IEth2Dai.json", | ||||
|         "test/generated-artifacts/IKyberNetwork.json", | ||||
|         "test/generated-artifacts/IMStable.json", | ||||
|         "test/generated-artifacts/IMooniswap.json", | ||||
|         "test/generated-artifacts/IMultiBridge.json", | ||||
|         "test/generated-artifacts/IShell.json", | ||||
|         "test/generated-artifacts/ISmoothy.json", | ||||
|         "test/generated-artifacts/IUniswapExchangeQuotes.json", | ||||
|         "test/generated-artifacts/IUniswapV2Router01.json", | ||||
|         "test/generated-artifacts/KyberDmmSampler.json", | ||||
| @@ -37,11 +35,9 @@ | ||||
|         "test/generated-artifacts/MStableSampler.json", | ||||
|         "test/generated-artifacts/MakerPSMSampler.json", | ||||
|         "test/generated-artifacts/MooniswapSampler.json", | ||||
|         "test/generated-artifacts/MultiBridgeSampler.json", | ||||
|         "test/generated-artifacts/NativeOrderSampler.json", | ||||
|         "test/generated-artifacts/SamplerUtils.json", | ||||
|         "test/generated-artifacts/ShellSampler.json", | ||||
|         "test/generated-artifacts/SmoothySampler.json", | ||||
|         "test/generated-artifacts/SwapRevertSampler.json", | ||||
|         "test/generated-artifacts/TestERC20BridgeSampler.json", | ||||
|         "test/generated-artifacts/TestNativeOrderSampler.json", | ||||
|         "test/generated-artifacts/TwoHopSampler.json", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user