diff --git a/contracts/zero-ex/contracts/src/samplers/ApproximateBuys.sol b/contracts/zero-ex/contracts/src/samplers/ApproximateBuys.sol new file mode 100644 index 0000000000..7f8d9ddff4 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/ApproximateBuys.sol @@ -0,0 +1,148 @@ +// 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++) { + uint256 eps = 0; + 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]) { + eps = + (buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS / + makerTokenAmounts[i]; + if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) { + break; + } + } + } + if (eps == 0 || 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; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BalanceChecker.sol b/contracts/zero-ex/contracts/src/samplers/BalanceChecker.sol new file mode 100644 index 0000000000..bfbc970e66 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BalanceChecker.sol @@ -0,0 +1,123 @@ +// 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; + +// ERC20 contract interface +abstract contract IToken { + /// @dev Query the balance of owner + /// @param _owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address _owner) public virtual view returns (uint256); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) public virtual view returns (uint256); +} + +contract BalanceChecker { + /* + Check the token balances of wallet-token pairs. + Pass 0xeee... as a "token" address to get ETH balance. + Possible error throws: + - extremely large arrays for user and or tokens (gas cost too high) + + Returns a one-dimensional that's user.length long. + */ + function balances(address[] calldata users, address[] calldata tokens) external view returns (uint256[] memory) { + // make sure the users array and tokens array are of equal length + require(users.length == tokens.length, "users array is a different length than the tokens array"); + + uint256[] memory addrBalances = new uint256[](users.length); + + for(uint i = 0; i < users.length; i++) { + if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + addrBalances[i] = IToken(tokens[i]).balanceOf(users[i]); + } else { + addrBalances[i] = users[i].balance; // ETH balance + } + } + + return addrBalances; + } + + /* + Check the token balances of wallet-token pairs with a spender contract for an allowance check. + Pass 0xeee... as a "token" address to get ETH balance. + Possible error throws: + - extremely large arrays for user and or tokens (gas cost too high) + + Returns a one-dimensional that's user.length long. It is the lesser of balance and allowance + */ + function getMinOfBalancesOrAllowances(address[] calldata users, address[] calldata tokens, address spender) external view returns (uint256[] memory) { + // make sure the users array and tokens array are of equal length + require(users.length == tokens.length, "users array is a different length than the tokens array"); + + uint256[] memory addrBalances = new uint256[](users.length); + + for(uint i = 0; i < users.length; i++) { + if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + uint256 balance; + uint256 allowance; + balance = IToken(tokens[i]).balanceOf(users[i]); + allowance = IToken(tokens[i]).allowance(users[i], spender); + if (allowance < balance) { + addrBalances[i] = allowance; + } else { + addrBalances[i] = balance; + } + } else { + addrBalances[i] = users[i].balance; // ETH balance + } + } + + return addrBalances; + } + + /* + Check the allowances of an array of owner-spender-tokens + + Returns 0 for 0xeee... (ETH) + Possible error throws: + - extremely large arrays for user and or tokens (gas cost too high) + + Returns a one-dimensional array that's owners.length long. + */ + function allowances(address[] calldata owners, address[] calldata spenders, address[] calldata tokens) external view returns (uint256[] memory) { + // make sure the arrays are all of equal length + require(owners.length == spenders.length, "all arrays must be of equal length"); + require(owners.length == tokens.length, "all arrays must be of equal length"); + + uint256[] memory addrAllowances = new uint256[](owners.length); + + for(uint i = 0; i < owners.length; i++) { + if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + addrAllowances[i] = IToken(tokens[i]).allowance(owners[i], spenders[i]); + } else { + // ETH + addrAllowances[i] = 0; + } + } + + return addrAllowances; + } + + +} diff --git a/contracts/zero-ex/contracts/src/samplers/BalancerSampler.sol b/contracts/zero-ex/contracts/src/samplers/BalancerSampler.sol new file mode 100644 index 0000000000..acd9e7acb1 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BalancerSampler.sol @@ -0,0 +1,197 @@ +// 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/IBalancer.sol"; + + +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; + } + + /// @dev Sample sell quotes from Balancer. + /// @param poolAddress Address of the Balancer pool to query. + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromBalancer( + address poolAddress, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + } + + /// @dev Sample buy quotes from Balancer. + /// @param poolAddress Address of the Balancer pool to query. + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromBalancer( + address poolAddress, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + IBalancer pool = IBalancer(poolAddress); + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) { + return takerTokenAmounts; + } + + 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) + { + // Handles this revert scenario: + // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443 + if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) { + break; + } + + 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; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BalancerV2BatchSampler.sol b/contracts/zero-ex/contracts/src/samplers/BalancerV2BatchSampler.sol new file mode 100644 index 0000000000..f99284861d --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BalancerV2BatchSampler.sol @@ -0,0 +1,105 @@ +// 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 "./interfaces/IBalancerV2Vault.sol"; +import "./BalancerV2Common.sol"; + +contract BalancerV2BatchSampler is BalancerV2Common { + + // Replaces amount for first step with each takerTokenAmount and calls queryBatchSwap using supplied steps + /// @dev Sample sell quotes from Balancer V2 supporting multihops. + /// @param swapSteps Array of swap steps (can be >= 1). + /// @param swapAssets Array of token address for swaps. + /// @param takerTokenAmounts Taker token sell amount for each sample. + function sampleMultihopSellsFromBalancerV2( + IBalancerV2Vault vault, + IBalancerV2Vault.BatchSwapStep[] memory swapSteps, + address[] memory swapAssets, + uint256[] memory takerTokenAmounts + ) + public + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + IBalancerV2Vault.FundManagement memory swapFunds = + _createSwapFunds(); + + for (uint256 i = 0; i < numSamples; i++) { + swapSteps[0].amount = 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 + // Note - queryBatchSwap will return a delta for each token in the assets array and last asset should be tokenOut + int256 amountOutFromPool = amounts[amounts.length - 1] * -1; + if (amountOutFromPool <= 0) { + break; + } + makerTokenAmounts[i] = uint256(amountOutFromPool); + } catch { + // Swallow failures, leaving all results as zero. + break; + } + } + } + + // Replaces amount for first step with each makerTokenAmount and calls queryBatchSwap using supplied steps + /// @dev Sample buy quotes from Balancer V2 supporting multihops. + /// @param swapSteps Array of swap steps (can be >= 1). + /// @param swapAssets Array of token address for swaps. + /// @param makerTokenAmounts Maker token buy amount for each sample. + function sampleMultihopBuysFromBalancerV2( + IBalancerV2Vault vault, + IBalancerV2Vault.BatchSwapStep[] memory swapSteps, + address[] memory swapAssets, + uint256[] memory makerTokenAmounts + ) + public + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + IBalancerV2Vault.FundManagement memory swapFunds = + _createSwapFunds(); + + for (uint256 i = 0; i < numSamples; i++) { + swapSteps[0].amount = makerTokenAmounts[i]; + try + // Uses GIVEN_OUT type for Buy + vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds) + // amounts represent pool balance deltas from the swap (incoming balance, outgoing balance) + returns (int256[] memory amounts) { + int256 amountIntoPool = amounts[0]; + if (amountIntoPool <= 0) { + break; + } + takerTokenAmounts[i] = uint256(amountIntoPool); + } catch { + // Swallow failures, leaving all results as zero. + break; + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BalancerV2Common.sol b/contracts/zero-ex/contracts/src/samplers/BalancerV2Common.sol new file mode 100644 index 0000000000..d01a1b2195 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BalancerV2Common.sol @@ -0,0 +1,41 @@ +// 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 "./interfaces/IBalancerV2Vault.sol"; + + +contract BalancerV2Common { + + function _createSwapFunds() + internal + view + returns (IBalancerV2Vault.FundManagement memory) + { + return + IBalancerV2Vault.FundManagement({ + sender: address(this), + fromInternalBalance: false, + recipient: payable(address(this)), + toInternalBalance: false + }); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BalancerV2Sampler.sol b/contracts/zero-ex/contracts/src/samplers/BalancerV2Sampler.sol new file mode 100644 index 0000000000..a6cfdaaa3b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BalancerV2Sampler.sol @@ -0,0 +1,142 @@ +// 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 "./SamplerUtils.sol"; +import "./interfaces/IBalancerV2Vault.sol"; +import "./BalancerV2Common.sol"; + + +contract BalancerV2Sampler is SamplerUtils, BalancerV2Common { + + /// @dev Sample sell quotes from Balancer V2. + /// @param poolInfo Struct with pool related data + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromBalancerV2( + IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + returns (uint256[] memory makerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); + address[] memory swapAssets = new address[](2); + swapAssets[0] = takerToken; + swapAssets[1] = 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[amounts.length - 1] * -1; + if (amountOutFromPool <= 0) { + break; + } + makerTokenAmounts[i] = uint256(amountOutFromPool); + } catch (bytes memory) { + // Swallow failures, leaving all results as zero. + break; + } + } + } + + /// @dev Sample buy quotes from Balancer V2. + /// @param poolInfo Struct with pool related data + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromBalancerV2( + IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + returns (uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); + address[] memory swapAssets = new address[](2); + swapAssets[0] = takerToken; + swapAssets[1] = 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( + IBalancerV2Vault.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; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BancorSampler.sol b/contracts/zero-ex/contracts/src/samplers/BancorSampler.sol new file mode 100644 index 0000000000..349c1c7631 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BancorSampler.sol @@ -0,0 +1,141 @@ +// 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/IBancor.sol"; + + +contract BancorSampler { + + /// @dev Base gas limit for Bancor calls. + uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k + + struct BancorSamplerOpts { + IBancorRegistry registry; + address[][] paths; + } + + /// @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). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return bancorNetwork the Bancor Network address + /// @return path the selected conversion path from bancor + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromBancor( + BancorSamplerOpts memory opts, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts) + { + if (opts.paths.length == 0) { + return (bancorNetwork, path, 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); + } + + /// @dev Sample buy quotes from Bancor. Unimplemented + /// @param opts BancorSamplerOpts The Bancor registry contract address and paths + /// @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 bancorNetwork the Bancor Network address + /// @return path the selected conversion path from bancor + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromBancor( + BancorSamplerOpts memory opts, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts) + { + } + + function _findBestPath( + BancorSamplerOpts memory opts, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + internal + view + returns (address bancorNetwork, address[] memory path) + { + bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK()); + if (opts.paths.length == 0) { + return (bancorNetwork, path); + } + uint256 maxBoughtAmount = 0; + // Find the best path by selling the largest taker amount + for (uint256 i = 0; i < opts.paths.length; i++) { + if (opts.paths[i].length < 2) { + continue; + } + + try + IBancorNetwork(bancorNetwork) + .rateByPath + {gas: BANCOR_CALL_GAS} + (opts.paths[i], takerTokenAmounts[takerTokenAmounts.length-1]) + returns (uint256 amount) + { + if (amount > maxBoughtAmount) { + maxBoughtAmount = amount; + path = opts.paths[i]; + } + } catch { + // Swallow failures, leaving all results as zero. + continue; + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/BancorV3Sampler.sol b/contracts/zero-ex/contracts/src/samplers/BancorV3Sampler.sol new file mode 100644 index 0000000000..3a54179d9c --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/BancorV3Sampler.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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/IBancorV3.sol"; + + +contract BancorV3Sampler +{ + /// @dev Gas limit for BancorV3 calls. + uint256 constant private BancorV3_CALL_GAS = 150e3; // 150k + + address constant public ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /// @dev Sample sell quotes from BancorV3. + /// @param weth The WETH contract address + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromBancorV3( + address weth, + address router, + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + if(path[0] == weth){ + path[0] = ETH; + } + if(path[1] == weth){ + path[1] = ETH; + } + + for (uint256 i = 0; i < numSamples; i++) { + try + IBancorV3(router).tradeOutputBySourceAmount(path[0], path[1], 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; + } + } + } + + /// @dev Sample buy quotes from BancorV3. + /// @param weth The WETH contract address + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromBancorV3( + address weth, + address router, + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + if(path[0] == weth){ + path[0] = ETH; + } + if(path[1] == weth){ + path[1] = ETH; + } + + for (uint256 i = 0; i < numSamples; i++) { + try + IBancorV3(router).tradeInputByTargetAmount(path[0], path[1], 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; + } + } + } +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/CompoundSampler.sol b/contracts/zero-ex/contracts/src/samplers/CompoundSampler.sol new file mode 100644 index 0000000000..2f68f59c7d --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/CompoundSampler.sol @@ -0,0 +1,96 @@ +// 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 "./SamplerUtils.sol"; +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; + +// Minimal CToken interface +interface ICToken { + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function exchangeRateStored() external view returns (uint); + function decimals() external view returns (uint8); +} + +contract CompoundSampler is SamplerUtils { + uint256 constant private EXCHANGE_RATE_SCALE = 1e10; + + function sampleSellsFromCompound( + ICToken cToken, + IERC20TokenV06 takerToken, + IERC20TokenV06 makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + // Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals + uint256 exchangeRate = cToken.exchangeRateStored(); + uint256 cTokenDecimals = uint256(cToken.decimals()); + + if (address(makerToken) == address(cToken)) { + // mint + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = (takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) / exchangeRate; + } + + } else if (address(takerToken) == address(cToken)) { + // redeem + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = (takerTokenAmounts[i] * exchangeRate) / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals); + } + } + } + + function sampleBuysFromCompound( + ICToken cToken, + IERC20TokenV06 takerToken, + IERC20TokenV06 makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + // Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals + uint256 exchangeRate = cToken.exchangeRateStored(); + uint256 cTokenDecimals = uint256(cToken.decimals()); + + if (address(makerToken) == address(cToken)) { + // mint + for (uint256 i = 0; i < numSamples; i++) { + takerTokenAmounts[i] = makerTokenAmounts[i] * exchangeRate / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals); + } + } else if (address(takerToken) == address(cToken)) { + // redeem + for (uint256 i = 0; i < numSamples; i++) { + takerTokenAmounts[i] = (makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals)/exchangeRate; + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/CurveSampler.sol b/contracts/zero-ex/contracts/src/samplers/CurveSampler.sol new file mode 100644 index 0000000000..d2f743d9db --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/CurveSampler.sol @@ -0,0 +1,161 @@ +// 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/ICurve.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract CurveSampler is + SamplerUtils, + ApproximateBuys +{ + /// @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+ + + /// @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 takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromCurve( + CurveInfo memory curveInfo, + int128 fromTokenIdx, + int128 toTokenIdx, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + } + + /// @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 makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromCurve( + CurveInfo memory curveInfo, + int128 fromTokenIdx, + int128 toTokenIdx, + uint256[] memory makerTokenAmounts + ) + public + view + returns (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]; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/DODOSampler.sol b/contracts/zero-ex/contracts/src/samplers/DODOSampler.sol new file mode 100644 index 0000000000..49e8ecf66b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/DODOSampler.sol @@ -0,0 +1,211 @@ +// 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 "./ApproximateBuys.sol"; +import "./SamplerUtils.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 +{ + + /// @dev Gas limit for DODO calls. + uint256 constant private DODO_CALL_GAS = 300e3; // 300k + struct DODOSamplerOpts { + address registry; + address helper; + } + + /// @dev Sample sell quotes from DODO. + /// @param opts DODOSamplerOpts DODO Registry and helper addresses + /// @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 sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromDODO( + DODOSamplerOpts memory opts, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, 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 + if (pool != address(0)) { + baseToken = takerToken; + sellBase = true; + } else { + pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken); + // No pool either direction + if (address(pool) == address(0)) { + return (sellBase, pool, 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; + } + } + } + + /// @dev Sample buy quotes from DODO. + /// @param opts DODOSamplerOpts DODO Registry and helper addresses + /// @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 sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromDODO( + DODOSamplerOpts memory opts, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, 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); + address baseToken; + // If pool exists we have the correct order of Base/Quote + if (pool != address(0)) { + baseToken = takerToken; + sellBase = true; + } else { + // Look up the pool from the maker/taker combination + pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken); + // No pool either direction + if (address(pool) == address(0)) { + return (sellBase, pool, 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 + }), + 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; + } + } + } + +} diff --git a/contracts/zero-ex/contracts/src/samplers/DODOV2Sampler.sol b/contracts/zero-ex/contracts/src/samplers/DODOV2Sampler.sol new file mode 100644 index 0000000000..6b99135cc9 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/DODOV2Sampler.sol @@ -0,0 +1,204 @@ +// 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 "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + +interface IDODOV2Registry { + function getDODOPool(address baseToken, address quoteToken) + external + view + 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 +{ + + /// @dev Gas limit for DODO V2 calls. + uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k + + /// @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. + /// @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 sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromDODOV2( + address registry, + uint256 offset, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, 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); + } + + 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; + } + } + } + + /// @dev Sample buy quotes from DODO. + /// @param registry Address of the registry to look up. + /// @param offset offset index for the pool in the registry. + /// @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 sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromDODOV2( + address registry, + uint256 offset, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + (pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken); + if (pool == address(0)) { + return (sellBase, pool, 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 + }), + 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, + address takerToken, + address makerToken + ) + internal + view + returns (address machine, bool sellBase) + { + // Query in base -> quote direction, if a pool is found then we are selling the base + address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken); + sellBase = true; + if (machines.length == 0) { + // Query in quote -> base direction, if a pool is found then we are selling the quote + machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken); + sellBase = false; + } + + if (offset >= machines.length) { + return (address(0), false); + } + + machine = machines[offset]; + } + +} diff --git a/contracts/zero-ex/contracts/src/samplers/ERC20BridgeSampler.sol b/contracts/zero-ex/contracts/src/samplers/ERC20BridgeSampler.sol new file mode 100644 index 0000000000..5f7d0d21fb --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/ERC20BridgeSampler.sol @@ -0,0 +1,103 @@ +// 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 "./BalancerSampler.sol"; +import "./BalancerV2Sampler.sol"; +import "./BalancerV2BatchSampler.sol"; +import "./BancorSampler.sol"; +import "./BancorV3Sampler.sol"; +import "./CompoundSampler.sol"; +import "./CurveSampler.sol"; +import "./DODOSampler.sol"; +import "./DODOV2Sampler.sol"; +import "./GMXSampler.sol"; +import "./KyberDmmSampler.sol"; +import "./LidoSampler.sol"; +import "./MakerPSMSampler.sol"; +import "./MStableSampler.sol"; +import "./MooniswapSampler.sol"; +import "./NativeOrderSampler.sol"; +import "./PlatypusSampler.sol"; +import "./ShellSampler.sol"; +import "./SynthetixSampler.sol"; +import "./TwoHopSampler.sol"; +import "./UniswapSampler.sol"; +import "./UniswapV2Sampler.sol"; +import "./UniswapV3Sampler.sol"; +import "./VelodromeSampler.sol"; +import "./WooPPSampler.sol"; +import "./UtilitySampler.sol"; + + +contract ERC20BridgeSampler is + BalancerSampler, + BalancerV2Sampler, + BalancerV2BatchSampler, + BancorSampler, + BancorV3Sampler, + CompoundSampler, + CurveSampler, + DODOSampler, + DODOV2Sampler, + GMXSampler, + KyberDmmSampler, + LidoSampler, + MakerPSMSampler, + MStableSampler, + MooniswapSampler, + NativeOrderSampler, + PlatypusSampler, + ShellSampler, + SynthetixSampler, + TwoHopSampler, + UniswapSampler, + UniswapV2Sampler, + UniswapV3Sampler, + VelodromeSampler, + WooPPSampler, + UtilitySampler +{ + + struct CallResults { + bytes data; + bool success; + } + + /// @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 + returns (CallResults[] memory callResults) + { + callResults = new CallResults[](callDatas.length); + for (uint256 i = 0; i != callDatas.length; ++i) { + callResults[i].success = true; + if (callDatas[i].length == 0) { + continue; + } + (callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]); + } + } + + receive() external payable {} +} diff --git a/contracts/zero-ex/contracts/src/samplers/FakeTaker.sol b/contracts/zero-ex/contracts/src/samplers/FakeTaker.sol new file mode 100644 index 0000000000..086c5a1590 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/FakeTaker.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +contract FakeTaker { + + struct Result { + bool success; + bytes resultData; + uint256 gasUsed; + } + + receive() payable external {} + + function execute(address payable to, bytes calldata data) + public + payable + returns (Result memory result) + { + uint256 gasBefore = gasleft(); + ( + result.success, + result.resultData + ) = to.call{ value: msg.value }(data); + result.gasUsed = gasBefore - gasleft(); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/GMXSampler.sol b/contracts/zero-ex/contracts/src/samplers/GMXSampler.sol new file mode 100644 index 0000000000..4ab8fb17e2 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/GMXSampler.sol @@ -0,0 +1,96 @@ +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import "./interfaces/IGMX.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + +contract GMXSampler is + SamplerUtils, + ApproximateBuys +{ + struct GMXInfo { + address reader; + address vault; + address[] path; + } + + function sampleSellsFromGMX( + address reader, + address vault, + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + try + IGMX(reader).getAmountOut(IVault(vault), path[0], path[1], takerTokenAmounts[i]) + returns (uint256 amountAfterFees, uint256 feeAmount) + { + makerTokenAmounts[i] = amountAfterFees; + // Break early if there are 0 amounts + if (makerTokenAmounts[i] == 0) { + break; + } + } catch (bytes memory) { + // Swallow failures, leaving all results as zero. + break; + } + } + } + + function sampleBuysFromGMX( + address reader, + address vault, + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + address[] memory invertBuyPath = new address[](2); + invertBuyPath[0] = path[1]; + invertBuyPath[1] = path[0]; + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(reader, vault, invertBuyPath), + takerTokenData: abi.encode(reader, vault, path), + getSellQuoteCallback: _sampleSellForApproximateBuyFromGMX + }), + makerTokenAmounts + ); + } + + + function _sampleSellForApproximateBuyFromGMX( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (address _reader, address _vault, address[] memory _path ) = abi.decode(takerTokenData, (address, address, address[])); + + (bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromGMX.selector, + _reader, + _vault, + _path, + _toSingleValueArray(sellAmount) + )); + if(!success) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256[]))[0]; + } + +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/KyberDmmSampler.sol b/contracts/zero-ex/contracts/src/samplers/KyberDmmSampler.sol new file mode 100644 index 0000000000..29ea8c01b3 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/KyberDmmSampler.sol @@ -0,0 +1,179 @@ +// 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; + +interface IKyberDmmPool { + + function totalSupply() + external + view + returns (uint256); +} + +interface IKyberDmmFactory { + + function getPools(address token0, address token1) + external + view + returns (address[] memory _tokenPools); +} + +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 +{ + /// @dev Gas limit for KyberDmm calls. + uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k + + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromKyberDmm( + address router, + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (address[] memory pools, 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; + } + } + } + + /// @dev Sample buy quotes from KyberDmm. + /// @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 pools The pool addresses involved in the multi path trade + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromKyberDmm( + address router, + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (address[] memory pools, uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + pools = _getKyberDmmPools(router, path); + if (pools.length == 0) { + return (pools, 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; + } + } + } + + function _getKyberDmmPools( + address router, + address[] memory path + ) + private + view + returns (address[] memory pools) + { + IKyberDmmFactory factory = IKyberDmmFactory(IKyberDmmRouter(router).factory()); + pools = new address[](path.length - 1); + for (uint256 i = 0; i < pools.length; i++) { + // find the best pool + address[] memory allPools; + try + factory.getPools + {gas: KYBER_DMM_CALL_GAS} + (path[i], path[i + 1]) + returns (address[] memory allPools) + { + if (allPools.length == 0) { + return new address[](0); + } + + uint256 maxSupply = 0; + for (uint256 j = 0; j < allPools.length; j++) { + uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply(); + if (totalSupply > maxSupply) { + maxSupply = totalSupply; + pools[i] = allPools[j]; + } + } + } catch (bytes memory) { + return new address[](0); + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/LidoSampler.sol b/contracts/zero-ex/contracts/src/samplers/LidoSampler.sol new file mode 100644 index 0000000000..27e23f703a --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/LidoSampler.sol @@ -0,0 +1,119 @@ +// 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 "./SamplerUtils.sol"; + + +interface IWstETH { + function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256); + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); +} + + +contract LidoSampler is SamplerUtils { + struct LidoInfo { + address stEthToken; + address wethToken; + address wstEthToken; + } + + /// @dev Sample sell quotes from Lido + /// @param lidoInfo Info regarding a specific Lido deployment + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromLido( + LidoInfo memory lidoInfo, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory) + { + _assertValidPair(makerToken, takerToken); + + if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) { + // Minting stETH is always 1:1 therefore we can just return the same amounts back. + return takerTokenAmounts; + } + + return _sampleSellsForWrapped(lidoInfo, takerToken, makerToken, takerTokenAmounts); + } + + /// @dev Sample buy quotes from Lido. + /// @param lidoInfo Info regarding a specific Lido deployment + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromLido( + LidoInfo memory lidoInfo, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory) + { + if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) { + // Minting stETH is always 1:1 therefore we can just return the same amounts back. + return makerTokenAmounts; + } + + // Swap out `makerToken` and `takerToken` and re-use `_sampleSellsForWrapped`. + return _sampleSellsForWrapped(lidoInfo, makerToken, takerToken, makerTokenAmounts); + } + + function _sampleSellsForWrapped( + LidoInfo memory lidoInfo, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) private view returns (uint256[] memory) { + IWstETH wstETH = IWstETH(lidoInfo.wstEthToken); + uint256 numSamples = takerTokenAmounts.length; + uint256[] memory makerTokenAmounts = new uint256[](numSamples); + + if (takerToken == lidoInfo.stEthToken && makerToken == lidoInfo.wstEthToken) { + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = wstETH.getWstETHByStETH(takerTokenAmounts[i]); + } + return makerTokenAmounts; + } + + if (takerToken == lidoInfo.wstEthToken && makerToken == lidoInfo.stEthToken) { + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = wstETH.getStETHByWstETH(takerTokenAmounts[i]); + } + return makerTokenAmounts; + } + + // Returns 0 values. + return makerTokenAmounts; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/MStableSampler.sol b/contracts/zero-ex/contracts/src/samplers/MStableSampler.sol new file mode 100644 index 0000000000..0947bc3767 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/MStableSampler.sol @@ -0,0 +1,127 @@ +// 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/IMStable.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract MStableSampler is + SamplerUtils, + ApproximateBuys +{ + /// @dev Default gas limit for mStable calls. + uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k + + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromMStable( + address router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + } + + /// @dev Sample buy quotes from 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 makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromMStable( + address router, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken, router), + takerTokenData: abi.encode(takerToken, router), + getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable + }), + 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; + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/MakerPSMSampler.sol b/contracts/zero-ex/contracts/src/samplers/MakerPSMSampler.sol new file mode 100644 index 0000000000..5adcf28716 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/MakerPSMSampler.sol @@ -0,0 +1,267 @@ +// 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 "./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 + ); +} + +contract MakerPSMSampler is + SamplerUtils +{ + using LibSafeMathV06 for uint256; + + /// @dev Information about which PSM module to use + struct MakerPsmInfo { + address psmAddress; + bytes32 ilkIdentifier; + 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 + + /// @dev Sample sell quotes from Maker PSM + function sampleSellsFromMakerPsm( + MakerPsmInfo memory psmInfo, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + + function sampleBuysFromMakerPsm( + MakerPsmInfo memory psmInfo, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (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; + } + + } + + 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; + } + +} diff --git a/contracts/zero-ex/contracts/src/samplers/MooniswapSampler.sol b/contracts/zero-ex/contracts/src/samplers/MooniswapSampler.sol new file mode 100644 index 0000000000..32ee080450 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/MooniswapSampler.sol @@ -0,0 +1,169 @@ +// 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/IMooniswap.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract MooniswapSampler is + SamplerUtils, + ApproximateBuys +{ + /// @dev Gas limit for Mooniswap calls. + uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k + + /// @dev Sample sell quotes from Mooniswap. + /// @param registry Address of the Mooniswap Registry. + /// @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 pool The contract address for the pool + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromMooniswap( + address registry, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (IMooniswap pool, 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 + 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; + } + } + + /// @dev Sample buy quotes from Mooniswap. + /// @param registry Address of the Mooniswap Registry. + /// @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 pool The contract address for the pool + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromMooniswap( + address registry, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (IMooniswap pool, uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(registry, makerToken), + takerTokenData: abi.encode(registry, takerToken), + getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap + }), + makerTokenAmounts + ); + + pool = IMooniswap( + IMooniswapRegistry(registry).pools(takerToken, makerToken) + ); + } + + function _sampleSellForApproximateBuyFromMooniswap( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address)); + (address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address)); + return sampleSingleSellFromMooniswapPool( + registry, + mooniswapTakerToken, + mooniswapMakerToken, + sellAmount + ); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/NativeOrderSampler.sol b/contracts/zero-ex/contracts/src/samplers/NativeOrderSampler.sol new file mode 100644 index 0000000000..43c84272fe --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/NativeOrderSampler.sol @@ -0,0 +1,239 @@ +// 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-erc20/contracts/src/v06/LibERC20TokenV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; + + +interface IExchange { + + enum OrderStatus { + INVALID, + FILLABLE, + FILLED, + CANCELLED, + EXPIRED + } + + /// @dev A standard OTC or OO limit order. + struct LimitOrder { + IERC20TokenV06 makerToken; + IERC20TokenV06 takerToken; + uint128 makerAmount; + uint128 takerAmount; + uint128 takerTokenFeeAmount; + address maker; + address taker; + address sender; + address feeRecipient; + bytes32 pool; + uint64 expiry; + uint256 salt; + } + + /// @dev An RFQ limit order. + struct RfqOrder { + IERC20TokenV06 makerToken; + IERC20TokenV06 takerToken; + uint128 makerAmount; + uint128 takerAmount; + address maker; + address taker; + address txOrigin; + bytes32 pool; + uint64 expiry; + uint256 salt; + } + + /// @dev Info on a limit or RFQ order. + struct OrderInfo { + bytes32 orderHash; + OrderStatus status; + uint128 takerTokenFilledAmount; + } + + /// @dev Allowed signature types. + enum SignatureType { + ILLEGAL, + INVALID, + EIP712, + ETHSIGN + } + + /// @dev Encoded EC signature. + struct Signature { + // How to validate the signature. + SignatureType signatureType; + // EC Signature data. + uint8 v; + // EC Signature data. + bytes32 r; + // EC Signature data. + bytes32 s; + } + + /// @dev Get the order info for a limit order. + /// @param order The limit order. + /// @return orderInfo Info about the order. + function getLimitOrderInfo(LimitOrder memory order) + external + view + returns (OrderInfo memory orderInfo); + + /// @dev Get order info, fillable amount, and signature validity for a limit order. + /// Fillable amount is determined using balances and allowances of the maker. + /// @param order The limit order. + /// @param signature The order signature. + /// @return orderInfo Info about the order. + /// @return actualFillableTakerTokenAmount How much of the order is fillable + /// based on maker funds, in taker tokens. + /// @return isSignatureValid Whether the signature is valid. + function getLimitOrderRelevantState( + LimitOrder memory order, + Signature calldata signature + ) + external + view + returns ( + OrderInfo memory orderInfo, + uint128 actualFillableTakerTokenAmount, + bool isSignatureValid + ); +} + +contract NativeOrderSampler { + using LibSafeMathV06 for uint256; + using LibBytesV06 for bytes; + + /// @dev Gas limit for calls to `getOrderFillableTakerAmount()`. + uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k + + /// @dev Queries the fillable taker asset amounts of native orders. + /// Effectively ignores orders that have empty signatures or + /// maker/taker asset amounts (returning 0). + /// @param orders Native limit orders to query. + /// @param orderSignatures Signatures for each respective order in `orders`. + /// @param exchange The V4 exchange. + /// @return orderFillableTakerAssetAmounts How much taker asset can be filled + /// by each order in `orders`. + function getLimitOrderFillableTakerAssetAmounts( + IExchange.LimitOrder[] memory orders, + IExchange.Signature[] memory orderSignatures, + IExchange exchange + ) + public + view + returns (uint256[] memory orderFillableTakerAssetAmounts) + { + orderFillableTakerAssetAmounts = new uint256[](orders.length); + for (uint256 i = 0; i != orders.length; i++) { + try + this.getLimitOrderFillableTakerAmount + {gas: DEFAULT_CALL_GAS} + ( + orders[i], + orderSignatures[i], + exchange + ) + returns (uint256 amount) + { + orderFillableTakerAssetAmounts[i] = amount; + } catch (bytes memory) { + // Swallow failures, leaving all results as zero. + orderFillableTakerAssetAmounts[i] = 0; + } + } + } + + /// @dev Queries the fillable taker asset amounts of native orders. + /// Effectively ignores orders that have empty signatures or + /// @param orders Native orders to query. + /// @param orderSignatures Signatures for each respective order in `orders`. + /// @param exchange The V4 exchange. + /// @return orderFillableMakerAssetAmounts How much maker asset can be filled + /// by each order in `orders`. + function getLimitOrderFillableMakerAssetAmounts( + IExchange.LimitOrder[] memory orders, + IExchange.Signature[] memory orderSignatures, + IExchange exchange + ) + public + view + returns (uint256[] memory orderFillableMakerAssetAmounts) + { + orderFillableMakerAssetAmounts = getLimitOrderFillableTakerAssetAmounts( + orders, + orderSignatures, + exchange + ); + // `orderFillableMakerAssetAmounts` now holds taker asset amounts, so + // convert them to maker asset amounts. + for (uint256 i = 0; i < orders.length; ++i) { + if (orderFillableMakerAssetAmounts[i] != 0) { + orderFillableMakerAssetAmounts[i] = LibMathV06.getPartialAmountCeil( + orderFillableMakerAssetAmounts[i], + orders[i].takerAmount, + orders[i].makerAmount + ); + } + } + } + + /// @dev Get the fillable taker amount of an order, taking into account + /// order state, maker fees, and maker balances. + function getLimitOrderFillableTakerAmount( + IExchange.LimitOrder memory order, + IExchange.Signature memory signature, + IExchange exchange + ) + virtual + public + view + returns (uint256 fillableTakerAmount) + { + if (signature.signatureType == IExchange.SignatureType.ILLEGAL || + signature.signatureType == IExchange.SignatureType.INVALID || + order.makerAmount == 0 || + order.takerAmount == 0) + { + return 0; + } + + ( + IExchange.OrderInfo memory orderInfo, + uint128 remainingFillableTakerAmount, + bool isSignatureValid + ) = exchange.getLimitOrderRelevantState(order, signature); + + if ( + orderInfo.status != IExchange.OrderStatus.FILLABLE || + !isSignatureValid || + order.makerToken == IERC20TokenV06(0) + ) { + return 0; + } + + fillableTakerAmount = uint256(remainingFillableTakerAmount); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/PlatypusSampler.sol b/contracts/zero-ex/contracts/src/samplers/PlatypusSampler.sol new file mode 100644 index 0000000000..bcf45b5a9f --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/PlatypusSampler.sol @@ -0,0 +1,89 @@ +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import "./interfaces/IPlatypus.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +contract PlatypusSampler is + SamplerUtils, + ApproximateBuys +{ + + function sampleSellsFromPlatypus( + address pool, + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + try + IPlatypus(pool).quotePotentialSwap(path[0], path[1], takerTokenAmounts[i]) + returns (uint256 amountAfterFees, uint256 feeAmount) + { + makerTokenAmounts[i] = amountAfterFees; + // Break early if there are 0 amounts + if (makerTokenAmounts[i] == 0) { + break; + } + } catch (bytes memory result) { + // Swallow failures, leaving all results as zero. + break; + } + } + } + + function sampleBuysFromPlatypus( + address pool, + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + address[] memory invertBuyPath = new address[](2); + invertBuyPath[0] = path[1]; + invertBuyPath[1] = path[0]; + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(pool, invertBuyPath), + takerTokenData: abi.encode(pool, path), + getSellQuoteCallback: _sampleSellForApproximateBuyFromPlatypus + }), + makerTokenAmounts + ); + } + + + function _sampleSellForApproximateBuyFromPlatypus( + bytes memory makerTokenData, + bytes memory takerTokenData, + uint256 sellAmount + ) + private + view + returns (uint256 buyAmount) + { + (address _pool, address[] memory _path ) = abi.decode(makerTokenData, (address, address[])); + + (bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromPlatypus.selector, + _pool, + _path, + _toSingleValueArray(sellAmount) + )); + if(!success) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256[]))[0]; + } +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/SamplerUtils.sol b/contracts/zero-ex/contracts/src/samplers/SamplerUtils.sol new file mode 100644 index 0000000000..b505512b6b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/SamplerUtils.sol @@ -0,0 +1,58 @@ +// 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"); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/ShellSampler.sol b/contracts/zero-ex/contracts/src/samplers/ShellSampler.sol new file mode 100644 index 0000000000..f580e12cc2 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/ShellSampler.sol @@ -0,0 +1,126 @@ +// 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 "./ApproximateBuys.sol"; +import "./interfaces/IShell.sol"; +import "./SamplerUtils.sol"; + + +contract ShellSampler is + SamplerUtils, + ApproximateBuys +{ + + struct ShellInfo { + address poolAddress; + } + + /// @dev Default gas limit for Shell calls. + uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k + + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromShell( + address pool, + address takerToken, + 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); + + 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; + } + } + } + + /// @dev Sample buy quotes from 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 makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromShell( + address pool, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + return _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken, pool), + takerTokenData: abi.encode(takerToken, pool), + getSellQuoteCallback: _sampleSellForApproximateBuyFromShell + }), + 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; + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/SynthetixSampler.sol b/contracts/zero-ex/contracts/src/samplers/SynthetixSampler.sol new file mode 100644 index 0000000000..38e45ebc6b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/SynthetixSampler.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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; + +interface IReadProxyAddressResolver { + function target() external view returns (address); +} + +interface IAddressResolver { + function getAddress(bytes32 name) external view returns (address); +} + +interface IExchanger { + // Ethereum Mainnet + function getAmountsForAtomicExchange( + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint256 amountReceived, + uint256 fee, + uint256 exchangeFeeRate + ); + + // Optimism + function getAmountsForExchange( + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint256 amountReceived, + uint256 fee, + uint256 exchangeFeeRate + ); +} + +contract SynthetixSampler { + + /// @dev Sample sell quotes from Synthetix Atomic Swap. + /// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell). + /// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order). + /// @return synthetix Synthetix address. + /// @return makerTokenAmounts Maker amounts bought at each taker token amount. + function sampleSellsFromSynthetix( + IReadProxyAddressResolver readProxy, + bytes32 takerTokenSymbol, + bytes32 makerTokenSymbol, + uint256[] memory takerTokenAmounts + ) public view returns (address synthetix, uint256[] memory makerTokenAmounts) { + synthetix = getSynthetixAddress(readProxy); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + if (numSamples == 0) { + return (synthetix, makerTokenAmounts); + } + + makerTokenAmounts[0] = exchange( + readProxy, + takerTokenAmounts[0], + takerTokenSymbol, + makerTokenSymbol + ); + + // Synthetix atomic swap has a fixed rate. Calculate the rest based on the first value (and save gas). + for (uint256 i = 1; i < numSamples; i++) { + makerTokenAmounts[i] = + (makerTokenAmounts[0] * takerTokenAmounts[i]) / + takerTokenAmounts[0]; + } + } + + /// @dev Sample buy quotes from Synthetix Atomic Swap. + /// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell). + /// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample (sorted in ascending order). + /// @return synthetix Synthetix address. + /// @return takerTokenAmounts Taker amounts sold at each maker token amount. + function sampleBuysFromSynthetix( + IReadProxyAddressResolver readProxy, + bytes32 takerTokenSymbol, + bytes32 makerTokenSymbol, + uint256[] memory makerTokenAmounts + ) public view returns (address synthetix, uint256[] memory takerTokenAmounts) { + synthetix = getSynthetixAddress(readProxy); + // Since Synthetix atomic have a fixed rate, we can pick any reasonablely size takerTokenAmount (fixed to 1 ether here) and calculate the rest. + uint256 amountReceivedForEther = exchange( + readProxy, + 1 ether, + takerTokenSymbol, + makerTokenSymbol + ); + + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + for (uint256 i = 0; i < numSamples; i++) { + takerTokenAmounts[i] = + (1 ether * makerTokenAmounts[i]) / + amountReceivedForEther; + } + } + + function exchange( + IReadProxyAddressResolver readProxy, + uint256 sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) private view returns (uint256 amountReceived) { + IExchanger exchanger = getExchanger(readProxy); + uint256 chainId; + assembly { + chainId := chainid() + } + + if (chainId == 1) { + (amountReceived, , ) = exchanger.getAmountsForAtomicExchange( + sourceAmount, + sourceCurrencyKey, + destinationCurrencyKey + ); + } else { + (amountReceived, , ) = exchanger.getAmountsForExchange( + sourceAmount, + sourceCurrencyKey, + destinationCurrencyKey + ); + } + } + + function getSynthetixAddress(IReadProxyAddressResolver readProxy) + private + view + returns (address) + { + return IAddressResolver(readProxy.target()).getAddress("Synthetix"); + } + + function getExchanger(IReadProxyAddressResolver readProxy) + private + view + returns (IExchanger) + { + return + IExchanger( + IAddressResolver(readProxy.target()).getAddress("Exchanger") + ); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/TwoHopSampler.sol b/contracts/zero-ex/contracts/src/samplers/TwoHopSampler.sol new file mode 100644 index 0000000000..a98fbc352c --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/TwoHopSampler.sol @@ -0,0 +1,124 @@ +// 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/LibBytesV06.sol"; + + +contract TwoHopSampler { + using LibBytesV06 for bytes; + + struct HopInfo { + uint256 sourceIndex; + bytes returnData; + } + + function sampleTwoHopSell( + bytes[] memory firstHopCalls, + bytes[] memory secondHopCalls, + uint256 sellAmount + ) + public + returns ( + HopInfo memory firstHop, + HopInfo memory secondHop, + uint256 buyAmount + ) + { + uint256 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]); + if (didSucceed) { + uint256 amount = returnData.readUint256(returnData.length - 32); + if (amount > intermediateAssetAmount) { + intermediateAssetAmount = amount; + firstHop.sourceIndex = i; + firstHop.returnData = returnData; + } + } + } + if (intermediateAssetAmount == 0) { + return (firstHop, secondHop, buyAmount); + } + for (uint256 j = 0; j != secondHopCalls.length; ++j) { + secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount); + (bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]); + if (didSucceed) { + uint256 amount = returnData.readUint256(returnData.length - 32); + if (amount > buyAmount) { + buyAmount = amount; + secondHop.sourceIndex = j; + secondHop.returnData = returnData; + } + } + } + } + + function sampleTwoHopBuy( + bytes[] memory firstHopCalls, + bytes[] memory secondHopCalls, + uint256 buyAmount + ) + public + returns ( + HopInfo memory firstHop, + HopInfo memory secondHop, + uint256 sellAmount + ) + { + sellAmount = uint256(-1); + uint256 intermediateAssetAmount = uint256(-1); + for (uint256 j = 0; j != secondHopCalls.length; ++j) { + secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount); + (bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]); + if (didSucceed) { + uint256 amount = returnData.readUint256(returnData.length - 32); + if ( + amount > 0 && + amount < intermediateAssetAmount + ) { + intermediateAssetAmount = amount; + secondHop.sourceIndex = j; + secondHop.returnData = returnData; + } + } + } + if (intermediateAssetAmount == uint256(-1)) { + return (firstHop, secondHop, sellAmount); + } + for (uint256 i = 0; i != firstHopCalls.length; ++i) { + firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount); + (bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]); + if (didSucceed) { + uint256 amount = returnData.readUint256(returnData.length - 32); + if ( + amount > 0 && + amount < sellAmount + ) { + sellAmount = amount; + firstHop.sourceIndex = i; + firstHop.returnData = returnData; + } + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/UniswapSampler.sol b/contracts/zero-ex/contracts/src/samplers/UniswapSampler.sol new file mode 100644 index 0000000000..329461cef9 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/UniswapSampler.sol @@ -0,0 +1,214 @@ +// 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/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); +} + + +contract UniswapSampler is + SamplerUtils +{ + /// @dev Gas limit for Uniswap calls. + uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k + + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromUniswap( + address router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + } + + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromUniswap( + address router, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (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)) + ); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/UniswapV2Sampler.sol b/contracts/zero-ex/contracts/src/samplers/UniswapV2Sampler.sol new file mode 100644 index 0000000000..de2f2ee29b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/UniswapV2Sampler.sol @@ -0,0 +1,102 @@ +// 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/IUniswapV2Router01.sol"; + + +contract UniswapV2Sampler +{ + /// @dev Gas limit for UniswapV2 calls. + uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k + + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromUniswapV2( + address router, + address[] memory path, + uint256[] memory takerTokenAmounts + ) + public + view + returns (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; + } + } + } + + /// @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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromUniswapV2( + address router, + address[] memory path, + uint256[] memory makerTokenAmounts + ) + public + view + returns (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; + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/UniswapV3Sampler.sol b/contracts/zero-ex/contracts/src/samplers/UniswapV3Sampler.sol new file mode 100644 index 0000000000..7eee3cedf6 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/UniswapV3Sampler.sol @@ -0,0 +1,363 @@ +// 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-erc20/contracts/src/v06/IERC20TokenV06.sol"; + +interface IUniswapV3QuoterV2 { + function factory() + external + view + returns (IUniswapV3Factory factory); + + // @notice Returns the amount out received for a given exact input swap without executing the swap + // @param path The path of the swap, i.e. each token pair and the pool fee + // @param amountIn The amount of the first token to swap + // @return amountOut The amount of the last token that would be received + // @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + // @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + // @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInput(bytes memory path, uint256 amountIn) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + // @notice Returns the amount in required for a given exact output swap without executing the swap + // @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order + // @param amountOut The amount of the last token to receive + // @return amountIn The amount of first token required to be paid + // @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + // @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + // @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutput(bytes memory path, uint256 amountOut) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +} + +interface IUniswapV3Factory { + function getPool(IERC20TokenV06 a, IERC20TokenV06 b, uint24 fee) + external + view + returns (IUniswapV3Pool pool); +} + +interface IUniswapV3Pool { + function token0() external view returns (IERC20TokenV06); + function token1() external view returns (IERC20TokenV06); + function fee() external view returns (uint24); +} + +contract UniswapV3Sampler +{ + /// @dev Gas limit for UniswapV3 calls. This is 100% a guess. + uint256 constant private QUOTE_GAS = 700e3; + + /// @dev Sample sell quotes from UniswapV3. + /// @param quoter UniswapV3 Quoter 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 uniswapGasUsed Estimated amount of gas used + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromUniswapV3( + IUniswapV3QuoterV2 quoter, + IERC20TokenV06[] memory path, + uint256[] memory takerTokenAmounts + ) + public + returns ( + bytes[] memory uniswapPaths, + uint256[] memory uniswapGasUsed, + uint256[] memory makerTokenAmounts + ) + { + IUniswapV3Pool[][] memory poolPaths = + _getValidPoolPaths(quoter.factory(), path, 0); + + makerTokenAmounts = new uint256[](takerTokenAmounts.length); + uniswapPaths = new bytes[](takerTokenAmounts.length); + uniswapGasUsed = new uint256[](takerTokenAmounts.length); + + for (uint256 i = 0; i < takerTokenAmounts.length; ++i) { + // Pick the best result from all the paths. + 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, + uint160[] memory, /* sqrtPriceX96AfterList */ + uint32[] memory, /* initializedTicksCrossedList */ + uint256 gasUsed + ) + { + if (topBuyAmount <= buyAmount) { + topBuyAmount = buyAmount; + uniswapPaths[i] = uniswapPath; + uniswapGasUsed[i] = gasUsed; + } + } catch {} + } + // Break early if we can't complete the sells. + if (topBuyAmount == 0) { + // HACK(kimpers): To avoid too many local variables, paths and gas used is set directly in the loop + // then reset if no valid valid quote was found + uniswapPaths[i] = ""; + uniswapGasUsed[i] = 0; + break; + } + makerTokenAmounts[i] = topBuyAmount; + } + } + + /// @dev Sample buy quotes from UniswapV3. + /// @param quoter UniswapV3 Quoter 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 uniswapGasUsed Estimated amount of gas used + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromUniswapV3( + IUniswapV3QuoterV2 quoter, + IERC20TokenV06[] memory path, + uint256[] memory makerTokenAmounts + ) + public + returns ( + bytes[] memory uniswapPaths, + uint256[] memory uniswapGasUsed, + uint256[] memory takerTokenAmounts + ) + { + IUniswapV3Pool[][] memory poolPaths = + _getValidPoolPaths(quoter.factory(), path, 0); + IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path); + + takerTokenAmounts = new uint256[](makerTokenAmounts.length); + uniswapPaths = new bytes[](makerTokenAmounts.length); + uniswapGasUsed = new uint256[](makerTokenAmounts.length); + + for (uint256 i = 0; i < makerTokenAmounts.length; ++i) { + // Pick the best result from all the paths. + 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, + uint160[] memory, /* sqrtPriceX96AfterList */ + uint32[] memory, /* initializedTicksCrossedList */ + uint256 gasUsed + ) + { + if (topSellAmount == 0 || topSellAmount >= sellAmount) { + topSellAmount = sellAmount; + // But the output path should still be encoded for sells. + uniswapPaths[i] = _toUniswapPath(path, poolPaths[j]); + uniswapGasUsed[i] = gasUsed; + } + } catch {} + } + // Break early if we can't complete the buys. + if (topSellAmount == 0) { + // HACK(kimpers): To avoid too many local variables, paths and gas used is set directly in the loop + // then reset if no valid valid quote was found + uniswapPaths[i] = ""; + uniswapGasUsed[i] = 0; + break; + } + takerTokenAmounts[i] = topSellAmount; + } + } + + function _getValidPoolPaths( + IUniswapV3Factory factory, + IERC20TokenV06[] memory tokenPath, + uint256 startIndex + ) + private + view + returns (IUniswapV3Pool[][] memory poolPaths) + { + require( + tokenPath.length - startIndex >= 2, + "UniswapV3Sampler/tokenPath too short" + ); + uint24[4] memory validPoolFees = [ + // The launch pool fees. Could get hairier if they add more. + uint24(0.0001e6), + uint24(0.0005e6), + uint24(0.003e6), + uint24(0.01e6) + ]; + IUniswapV3Pool[] memory validPools = + new IUniswapV3Pool[](validPoolFees.length); + uint256 numValidPools = 0; + { + IERC20TokenV06 inputToken = tokenPath[startIndex]; + IERC20TokenV06 outputToken = tokenPath[startIndex + 1]; + for (uint256 i = 0; i < validPoolFees.length; ++i) { + IUniswapV3Pool pool = + factory.getPool(inputToken, outputToken, validPoolFees[i]); + if (_isValidPool(pool)) { + validPools[numValidPools++] = pool; + } + } + } + if (numValidPools == 0) { + // No valid pools for this hop. + return poolPaths; + } + if (startIndex + 2 == tokenPath.length) { + // End of path. + poolPaths = new IUniswapV3Pool[][](numValidPools); + for (uint256 i = 0; i < numValidPools; ++i) { + poolPaths[i] = new IUniswapV3Pool[](1); + poolPaths[i][0] = validPools[i]; + } + return poolPaths; + } + // Get paths for subsequent hops. + IUniswapV3Pool[][] memory subsequentPoolPaths = + _getValidPoolPaths(factory, tokenPath, startIndex + 1); + if (subsequentPoolPaths.length == 0) { + // Could not complete the path. + return poolPaths; + } + // Combine our pools with the next hop paths. + poolPaths = new IUniswapV3Pool[][]( + numValidPools * subsequentPoolPaths.length + ); + for (uint256 i = 0; i < numValidPools; ++i) { + for (uint256 j = 0; j < subsequentPoolPaths.length; ++j) { + uint256 o = i * subsequentPoolPaths.length + j; + // Prepend pool to the subsequent path. + poolPaths[o] = + new IUniswapV3Pool[](1 + subsequentPoolPaths[j].length); + poolPaths[o][0] = validPools[i]; + for (uint256 k = 0; k < subsequentPoolPaths[j].length; ++k) { + poolPaths[o][1 + k] = subsequentPoolPaths[j][k]; + } + } + } + return poolPaths; + } + + function _reverseTokenPath(IERC20TokenV06[] memory tokenPath) + private + pure + returns (IERC20TokenV06[] memory reversed) + { + reversed = new IERC20TokenV06[](tokenPath.length); + for (uint256 i = 0; i < tokenPath.length; ++i) { + reversed[i] = tokenPath[tokenPath.length - i - 1]; + } + } + + function _reversePoolPath(IUniswapV3Pool[] memory poolPath) + private + pure + returns (IUniswapV3Pool[] memory reversed) + { + reversed = new IUniswapV3Pool[](poolPath.length); + for (uint256 i = 0; i < poolPath.length; ++i) { + reversed[i] = poolPath[poolPath.length - i - 1]; + } + } + + function _isValidPool(IUniswapV3Pool pool) + private + view + returns (bool isValid) + { + // Check if it has been deployed. + { + uint256 codeSize; + assembly { + codeSize := extcodesize(pool) + } + if (codeSize == 0) { + return false; + } + } + // Must have a balance of both tokens. + if (pool.token0().balanceOf(address(pool)) == 0) { + return false; + } + if (pool.token1().balanceOf(address(pool)) == 0) { + return false; + } + return true; + } + + function _toUniswapPath( + IERC20TokenV06[] memory tokenPath, + IUniswapV3Pool[] memory poolPath + ) + private + view + returns (bytes memory uniswapPath) + { + require( + tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1, + "UniswapV3Sampler/invalid path lengths" + ); + // Uniswap paths are tightly packed as: + // [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...] + uniswapPath = new bytes(tokenPath.length * 20 + poolPath.length * 3); + uint256 o; + assembly { o := add(uniswapPath, 32) } + for (uint256 i = 0; i < tokenPath.length; ++i) { + if (i > 0) { + uint24 poolFee = poolPath[i - 1].fee(); + assembly { + mstore(o, shl(232, poolFee)) + o := add(o, 3) + } + } + IERC20TokenV06 token = tokenPath[i]; + assembly { + mstore(o, shl(96, token)) + o := add(o, 20) + } + } + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/UtilitySampler.sol b/contracts/zero-ex/contracts/src/samplers/UtilitySampler.sol new file mode 100644 index 0000000000..bbc3c5a1ad --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/UtilitySampler.sol @@ -0,0 +1,95 @@ + +// 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-erc20/contracts/src/v06/LibERC20TokenV06.sol"; + +contract UtilitySampler { + + using LibERC20TokenV06 for IERC20TokenV06; + + IERC20TokenV06 private immutable UTILITY_ETH_ADDRESS = IERC20TokenV06(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + + function getTokenDecimals(IERC20TokenV06[] memory tokens) + public + view + returns (uint256[] memory decimals) + { + decimals = new uint256[](tokens.length); + for (uint256 i = 0; i != tokens.length; i++) { + decimals[i] = tokens[i] == UTILITY_ETH_ADDRESS + ? 18 + : tokens[i].compatDecimals(); + } + } + + function getBalanceOf(IERC20TokenV06[] memory tokens, address account) + public + view + returns (uint256[] memory balances) + { + balances = new uint256[](tokens.length); + for (uint256 i = 0; i != tokens.length; i++) { + balances[i] = tokens[i] == UTILITY_ETH_ADDRESS + ? account.balance + : tokens[i].compatBalanceOf(account); + } + } + + function getAllowanceOf(IERC20TokenV06[] memory tokens, address account, address spender) + public + view + returns (uint256[] memory allowances) + { + allowances = new uint256[](tokens.length); + for (uint256 i = 0; i != tokens.length; i++) { + allowances[i] = tokens[i] == UTILITY_ETH_ADDRESS + ? 0 + : tokens[i].compatAllowance(account, spender); + } + } + + function isContract(address account) + public + view + returns (bool) + { + uint256 size; + assembly { size := extcodesize(account) } + return size > 0; + } + + function getGasLeft() + public + returns (uint256) + { + return gasleft(); + } + + function getBlockNumber() + public + view + returns (uint256) + { + return block.number; + } +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/VelodromeSampler.sol b/contracts/zero-ex/contracts/src/samplers/VelodromeSampler.sol new file mode 100644 index 0000000000..8640f9e1c7 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/VelodromeSampler.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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 './ApproximateBuys.sol'; +import './SamplerUtils.sol'; + +struct VeloRoute { + address from; + address to; + bool stable; +} + +interface IVelodromeRouter { + function getAmountOut( + uint256 amountIn, + address tokenIn, + address tokenOut + ) external view returns (uint256 amount, bool stable); + + function getAmountsOut(uint256 amountIn, VeloRoute[] calldata routes) + external + view + returns (uint256[] memory amounts); +} + +contract VelodromeSampler is SamplerUtils, ApproximateBuys { + /// @dev Sample sell quotes from Velodrome + /// @param router Address of Velodrome 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 (sorted in ascending order). + /// @return stable Whether the pool is a stable pool (vs volatile). + /// @return makerTokenAmounts Maker amounts bought at each taker token amount. + function sampleSellsFromVelodrome( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) public view returns (bool stable, uint256[] memory makerTokenAmounts) { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + // Sampling should not mix stable and volatile pools. + // Find the most liquid pool based on max(takerTokenAmounts) and stick with it. + stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts); + VeloRoute[] memory routes = new VeloRoute[](1); + routes[0] = VeloRoute({ from: takerToken, to: makerToken, stable: stable }); + + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1]; + // Break early if there are 0 amounts + if (makerTokenAmounts[i] == 0) { + break; + } + } + } + + /// @dev Sample buy quotes from Velodrome. + /// @param router Address of Velodrome router. + /// @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 stable Whether the pool is a stable pool (vs volatile). + /// @return takerTokenAmounts Taker amounts sold at each maker token amount. + function sampleBuysFromVelodrome( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) public view returns (bool stable, uint256[] memory takerTokenAmounts) { + _assertValidPair(makerToken, takerToken); + + // Sampling should not mix stable and volatile pools. + // Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it. + stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts); + + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + takerTokenData: abi.encode(router, VeloRoute({ from: takerToken, to: makerToken, stable: stable })), + makerTokenData: abi.encode(router, VeloRoute({ from: makerToken, to: takerToken, stable: stable })), + getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromVelodrome( + bytes memory takerTokenData, + bytes memory, /* makerTokenData */ + uint256 sellAmount + ) internal view returns (uint256) { + (IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute)); + + VeloRoute[] memory routes = new VeloRoute[](1); + routes[0] = route; + return router.getAmountsOut(sellAmount, routes)[1]; + } + + /// @dev Returns whether the most liquid pool is a stable pool. + /// @param router Address of Velodrome 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 buy amount for each sample (sorted in ascending order) + /// @return stable Whether the pool is a stable pool (vs volatile). + function _isMostLiquidPoolStablePool( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) internal view returns (bool stable) { + uint256 numSamples = takerTokenAmounts.length; + (, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken); + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/WooPPSampler.sol b/contracts/zero-ex/contracts/src/samplers/WooPPSampler.sol new file mode 100644 index 0000000000..4d40c6dde7 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/WooPPSampler.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; +import "./SamplerUtils.sol"; +import "./ApproximateBuys.sol"; + +interface IWooPP { + /// @dev get the quote token address (immutable) + /// @return address of quote token + function quoteToken() external view returns (address); + + /// @dev Query the amount for selling the base token amount. + /// @param baseToken the base token to sell + /// @param baseAmount the amount to sell + /// @return quoteAmount the swapped quote amount + function querySellBase(address baseToken, uint256 baseAmount) external view returns (uint256 quoteAmount); + + /// @dev Query the amount for selling the quote token. + /// @param baseToken the base token to receive (buy) + /// @param quoteAmount the amount to sell + /// @return baseAmount the swapped base token amount + function querySellQuote(address baseToken, uint256 quoteAmount) external view returns (uint256 baseAmount); +} + +contract WooPPSampler is SamplerUtils, ApproximateBuys{ + + function query( + uint amountIn, + address tokenIn, + address tokenOut, + address pool + ) internal view returns (uint256 amountOut) { + if (amountIn == 0) { + return 0; + } + address quoteToken = IWooPP(pool).quoteToken(); + if (tokenIn == quoteToken) { + amountOut = IWooPP(pool).querySellQuote(tokenOut, amountIn); + } else if (tokenOut == quoteToken) { + amountOut = IWooPP(pool).querySellBase(tokenIn, amountIn); + } else { + uint quoteAmount = IWooPP(pool).querySellBase(tokenIn, amountIn); + amountOut = IWooPP(pool).querySellQuote(tokenOut, quoteAmount); + } + } + + /// @dev Sample sell quotes from WooFI. + /// @param pool Address of the pool we are sampling from + /// @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 (sorted in ascending order). + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromWooPP( + address pool, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = query(takerTokenAmounts[i], takerToken, makerToken, pool); + + if (makerTokenAmounts[i] == 0) { + break; + } + } + } + + /// @dev Sample buy quotes from WooFI. + /// @param pool Address of the pool we are sampling from + /// @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 (sorted in ascending order). + /// @return takerTokenAmounts Taker amounts bought at each taker token + /// amount. + function sampleBuysFromWooPP( + address pool, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + takerTokenData: abi.encode(pool,takerToken, makerToken), + makerTokenData: abi.encode(pool, makerToken, takerToken), + getSellQuoteCallback: _sampleSellForApproximateBuyFromWoofi + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromWoofi( + bytes memory takerTokenData, + bytes memory makerTokenData, + uint256 sellAmount + ) internal view returns (uint256) { + (address _pool, address _takerToken, address _makerToken) = abi.decode(takerTokenData, (address, address, address)); + (bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector( + this.sampleSellsFromWooPP.selector, + _pool, + _takerToken, + _makerToken, + _toSingleValueArray(sellAmount) + )); + if(!success) { + return 0; + } + return abi.decode(resultData, (uint256[]))[0]; + } +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancer.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancer.sol new file mode 100644 index 0000000000..31a90776c5 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancer.sol @@ -0,0 +1,44 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancerV2Vault.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancerV2Vault.sol new file mode 100644 index 0000000000..14f45f19b8 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IBalancerV2Vault.sol @@ -0,0 +1,54 @@ +// 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; + +/// @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 } + + struct BatchSwapStep { + bytes32 poolId; + uint256 assetInIndex; + uint256 assetOutIndex; + uint256 amount; + bytes userData; + } + + struct FundManagement { + address sender; + bool fromInternalBalance; + address payable recipient; + bool toInternalBalance; + } + + struct BalancerV2PoolInfo { + bytes32 poolId; + address vault; + } + + function queryBatchSwap( + SwapKind kind, + BatchSwapStep[] calldata swaps, + address[] calldata assets, + FundManagement calldata funds + ) external returns (int256[] memory assetDeltas); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IBancor.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IBancor.sol new file mode 100644 index 0000000000..df64503d80 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IBancor.sol @@ -0,0 +1,33 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IBancorV3.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IBancorV3.sol new file mode 100644 index 0000000000..0b5bed9f8f --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IBancorV3.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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; + +interface IBancorV3 { + + /** + * @dev returns the output amount when trading by providing the source amount + */ + function tradeOutputBySourceAmount( + address sourceToken, + address targetToken, + uint256 sourceAmount + ) external view returns (uint256); + + /** + * @dev returns the input amount when trading by providing the target amount + */ + function tradeInputByTargetAmount( + address sourceToken, + address targetToken, + uint256 targetAmount + ) external view returns (uint256); + +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/ICurve.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/ICurve.sol new file mode 100644 index 0000000000..da591fe8fd --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/ICurve.sol @@ -0,0 +1,72 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IGMX.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IGMX.sol new file mode 100644 index 0000000000..12b1c3d87c --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IGMX.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +interface IGMX { + function getMaxAmountIn(IVault _vault, address _tokenIn, address _tokenOut) + external + view + returns (uint256); + + function getAmountOut(IVault _vault, address _tokenIn, address _tokenOut, uint256 _amountIn) + external + view + returns (uint256, uint256); +} + +interface IVault { + function getFeeBasisPoints(address _token, uint256 _usdgDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment) external view returns (uint256); + function stableSwapFeeBasisPoints() external view returns (uint256); + function stableTokens(address _token) external view returns (bool); + function tokenDecimals(address _token) external view returns (uint256); + function getMaxPrice(address _token) external view returns (uint256); + function getMinPrice(address _token) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IMStable.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IMStable.sol new file mode 100644 index 0000000000..cae1bbb99b --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IMStable.sol @@ -0,0 +1,33 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IMooniswap.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IMooniswap.sol new file mode 100644 index 0000000000..93a8fff596 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IMooniswap.sol @@ -0,0 +1,38 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IMultiBridge.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IMultiBridge.sol new file mode 100644 index 0000000000..1bad277f59 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IMultiBridge.sol @@ -0,0 +1,59 @@ +// 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); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IPlatypus.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IPlatypus.sol new file mode 100644 index 0000000000..d764de70f4 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IPlatypus.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.6; + +interface IPlatypus { + function quotePotentialSwap( + address fromToken, + address toToken, + uint256 fromAmount + ) external view returns (uint256 potentialOutcome, uint256 haircut); + + function assetOf(address token) external view returns (address); +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IShell.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IShell.sol new file mode 100644 index 0000000000..103b2778af --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IShell.sol @@ -0,0 +1,43 @@ +// 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); +} + diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapExchangeQuotes.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapExchangeQuotes.sol new file mode 100644 index 0000000000..5143824643 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapExchangeQuotes.sol @@ -0,0 +1,52 @@ +// 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 IUniswapExchangeQuotes { + + function getEthToTokenInputPrice( + uint256 ethSold + ) + external + view + returns (uint256 tokensBought); + + function getEthToTokenOutputPrice( + uint256 tokensBought + ) + external + view + returns (uint256 ethSold); + + function getTokenToEthInputPrice( + uint256 tokensSold + ) + external + view + returns (uint256 ethBought); + + function getTokenToEthOutputPrice( + uint256 ethBought + ) + external + view + returns (uint256 tokensSold); +} diff --git a/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapV2Router01.sol b/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapV2Router01.sol new file mode 100644 index 0000000000..ab63971992 --- /dev/null +++ b/contracts/zero-ex/contracts/src/samplers/interfaces/IUniswapV2Router01.sol @@ -0,0 +1,34 @@ +// 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 IUniswapV2Router01 { + + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + returns (uint256[] memory amounts); +} diff --git a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol index 772f5ccb60..250e1149ba 100644 --- a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol @@ -1,20 +1,15 @@ // 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.5; @@ -209,6 +204,7 @@ contract FillQuoteTransformer is Transformer { } state.ethRemaining = address(this).balance; + // Fill the orders. for (uint256 i = 0; i < data.fillSequence.length; ++i) { // Check if we've hit our targets. @@ -441,4 +437,4 @@ contract FillQuoteTransformer is Transformer { } return rawAmount; } -} +} \ No newline at end of file diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol index e0747bd6b8..6f5d1b388c 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/ArbitrumBridgeAdapter.sol @@ -1,20 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 /* - Copyright 2022 ZeroEx Intl. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ pragma solidity ^0.6.5; diff --git a/contracts/zero-ex/contracts/test/foundry/LiveBridgeOrderTests.sol b/contracts/zero-ex/contracts/test/foundry/LiveBridgeOrderTests.sol index e6fe37ec42..27b715b8bb 100644 --- a/contracts/zero-ex/contracts/test/foundry/LiveBridgeOrderTests.sol +++ b/contracts/zero-ex/contracts/test/foundry/LiveBridgeOrderTests.sol @@ -12,7 +12,7 @@ import "src/transformers/WethTransformer.sol"; import "src/transformers/FillQuoteTransformer.sol"; import "src/transformers/bridges/BridgeProtocols.sol"; import "src/features/OtcOrdersFeature.sol"; -import "src/sampler/UniswapV2Sampler.sol"; +import "@0x/samplers/UniswapV2Sampler.sol"; import "forge-std/StdJson.sol"; contract ETHToERC20TransformTest is Test, ForkUtils, TestUtils { diff --git a/contracts/zero-ex/contracts/test/foundry/OtcOrderTests.sol b/contracts/zero-ex/contracts/test/foundry/OtcOrderTests.sol index a9d763ba94..7d3deb32a7 100644 --- a/contracts/zero-ex/contracts/test/foundry/OtcOrderTests.sol +++ b/contracts/zero-ex/contracts/test/foundry/OtcOrderTests.sol @@ -12,7 +12,7 @@ import "src/transformers/WethTransformer.sol"; import "src/transformers/FillQuoteTransformer.sol"; import "src/transformers/bridges/BridgeProtocols.sol"; import "src/features/OtcOrdersFeature.sol"; -import "src/sampler/UniswapV2Sampler.sol"; +import "@0x/samplers/UniswapV2Sampler.sol"; import "forge-std/StdJson.sol"; contract NativeTokenToERC20WithOtcTest is Test, ForkUtils, TestUtils { diff --git a/contracts/zero-ex/contracts/test/foundry/utils/ForkUtils.sol b/contracts/zero-ex/contracts/test/foundry/utils/ForkUtils.sol index 8b4826da34..dee42b4733 100644 --- a/contracts/zero-ex/contracts/test/foundry/utils/ForkUtils.sol +++ b/contracts/zero-ex/contracts/test/foundry/utils/ForkUtils.sol @@ -21,9 +21,6 @@ pragma solidity ^0.6; pragma experimental ABIEncoderV2; import "forge-std/Test.sol"; -<<<<<<< HEAD - -======= import "src/features/TransformERC20Feature.sol"; import "src/external/TransformerDeployer.sol"; import "src/transformers/WethTransformer.sol"; @@ -78,11 +75,8 @@ struct LiquiditySources { interface IFQT{ function bridgeAdapter() external returns (address); } ->>>>>>> 725dfe4db (working bridge Fills through weth transformer) contract ForkUtils is Test { -<<<<<<< HEAD -======= using stdJson for string; //forked providers for each chain @@ -106,19 +100,8 @@ contract ForkUtils is Test { //special fork block number for fantom since it produces blocks faster and more frequently uint256[] blockNumber = [forkBlock, forkBlock, 33447149, forkBlock, 32000000, forkBlock, forkBlock]; ->>>>>>> 06f73f9d5 (added working otc fill through transformERC20 in FQT) /// Only run this function if the block number // is greater than some constant for Ethereum Mainnet -<<<<<<< HEAD - modifier onlyForked { - if (block.number >= 14206900) { - _; - } else { - emit log_string("Requires fork mode, skipping"); - } - } -} -======= string addressesJson; string tokensJson; @@ -237,4 +220,3 @@ contract ForkUtils is Test { } } } ->>>>>>> 725dfe4db (working bridge Fills through weth transformer) diff --git a/contracts/zero-ex/contracts/test/foundry/utils/TestUtils.sol b/contracts/zero-ex/contracts/test/foundry/utils/TestUtils.sol index 1453fd7bf7..276a3e6c2f 100644 --- a/contracts/zero-ex/contracts/test/foundry/utils/TestUtils.sol +++ b/contracts/zero-ex/contracts/test/foundry/utils/TestUtils.sol @@ -60,7 +60,6 @@ contract TestUtils is Test { return nstr; } -<<<<<<< HEAD function _findTransformerNonce( address transformer, address deployer @@ -69,9 +68,6 @@ contract TestUtils is Test { pure returns (uint32) { -======= - function _findTransformerNonce(address transformer, address deployer) internal returns (uint32) { ->>>>>>> 725dfe4db (working bridge Fills through weth transformer) address current; for (uint32 i = 0; i < 1024; i++) { current = LibERC20Transformer.getDeployedAddress(deployer, i); diff --git a/contracts/zero-ex/foundry.toml b/contracts/zero-ex/foundry.toml index 715b0623b4..d4c42187c7 100644 --- a/contracts/zero-ex/foundry.toml +++ b/contracts/zero-ex/foundry.toml @@ -3,18 +3,12 @@ src = 'contracts/src' out = 'foundry-artifacts' test = 'contracts/test/foundry' libs = ["contracts/deps/", "../utils/contracts/src/"] -<<<<<<< HEAD -remappings = ['@0x/contracts-utils/=../utils/', '@0x/contracts-erc20/=../erc20/', 'src/=./contracts/src'] -cache_path = 'foundry-cache' -optimizer_runs = 1000000 - -======= remappings = [ '@0x/contracts-utils/=../utils/', '@0x/contracts-erc20/=../erc20/', 'src/=./contracts/src', '@0x/contract-addresses/=../../packages/contract-addresses', - 'samplers/=./contracts/src/sampler', + '@0x/samplers/=contracts/src/samplers', ] cache_path = 'foundry-cache' optimizer_runs = 1000000 @@ -31,4 +25,3 @@ fantom = "https://rpc.ftm.tools/" mainnet = "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" optimism = "https://mainnet.optimism.io" polygon = "https://polygon-rpc.com" ->>>>>>> 06f73f9d5 (added working otc fill through transformERC20 in FQT)