diff --git a/contracts/asset-proxy/contracts/src/bridges/ChaiBridge.sol b/contracts/asset-proxy/contracts/src/bridges/ChaiBridge.sol index 93ff9f9ec8..9ff2908aff 100644 --- a/contracts/asset-proxy/contracts/src/bridges/ChaiBridge.sol +++ b/contracts/asset-proxy/contracts/src/bridges/ChaiBridge.sol @@ -35,7 +35,7 @@ contract ChaiBridge is /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. - /// @return success The magic bytes `0x37708e9b` if successful. + /// @return success The magic bytes `0xdc1600f3` if successful. function bridgeTransferFrom( address /* tokenAddress */, address from, diff --git a/contracts/asset-proxy/contracts/src/interfaces/IERC20Bridge.sol b/contracts/asset-proxy/contracts/src/interfaces/IERC20Bridge.sol index c32fa76477..1e5ddef19e 100644 --- a/contracts/asset-proxy/contracts/src/interfaces/IERC20Bridge.sol +++ b/contracts/asset-proxy/contracts/src/interfaces/IERC20Bridge.sol @@ -30,7 +30,7 @@ contract IERC20Bridge { /// @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 `0x37708e9b` if successful. + /// @return success The magic bytes `0xdc1600f3` if successful. function bridgeTransferFrom( address tokenAddress, address from, diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.json b/contracts/erc20-bridge-sampler/CHANGELOG.json index 43241532af..dd976b0b37 100644 --- a/contracts/erc20-bridge-sampler/CHANGELOG.json +++ b/contracts/erc20-bridge-sampler/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.5.0", + "changes": [ + { + "note": "Add generic liquidity provider sampling", + "pr": 2487 + } + ] + }, { "timestamp": 1582677073, "version": "1.4.2", diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 9ce4e748eb..559d3f3c63 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -30,6 +30,7 @@ import "./IEth2Dai.sol"; import "./IKyberNetwork.sol"; import "./IUniswapExchangeQuotes.sol"; import "./ICurve.sol"; +import "./ILiquidityProvider.sol"; contract ERC20BridgeSampler is @@ -47,6 +48,8 @@ contract ERC20BridgeSampler is /// @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 internal CURVE_CALL_GAS = 600e3; // 600k + /// @dev Default gas limit for liquidity provider calls. + uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k /// @dev Call multiple public functions on this contract in a single transaction. /// @param callDatas ABI-encoded call data for each function call. @@ -431,6 +434,84 @@ contract ERC20BridgeSampler is } } + /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. + /// @param providerAddress Address of the liquidity provider 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 sampleSellsFromLiquidityProvider( + address providerAddress, + 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++) { + (bool didSucceed, bytes memory resultData) = + providerAddress.staticcall.gas(DEFAULT_CALL_GAS)( + abi.encodeWithSelector( + ILiquidityProvider(0).getSellQuote.selector, + takerToken, + makerToken, + takerTokenAmounts[i] + )); + uint256 buyAmount = 0; + if (didSucceed) { + buyAmount = abi.decode(resultData, (uint256)); + } else { + // Exit early if the amount is too high for the liquidity provider to serve + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. + /// @param providerAddress Address of the liquidity provider 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 sampleBuysFromLiquidityProvider( + address providerAddress, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (bool didSucceed, bytes memory resultData) = + providerAddress.staticcall.gas(DEFAULT_CALL_GAS)( + abi.encodeWithSelector( + ILiquidityProvider(0).getBuyQuote.selector, + takerToken, + makerToken, + makerTokenAmounts[i] + )); + uint256 sellAmount = 0; + if (didSucceed) { + sellAmount = abi.decode(resultData, (uint256)); + } else { + // Exit early if the amount is too high for the liquidity provider to serve + break; + } + takerTokenAmounts[i] = sellAmount; + } + } + /// @dev Overridable way to get token decimals. /// @param tokenAddress Address of the token. /// @return decimals The decimal places for the token. diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol index 5d0435aab2..7c12da6260 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol @@ -149,4 +149,38 @@ interface IERC20BridgeSampler { external view returns (uint256[] memory makerTokenAmounts); + + /// @dev Sample sell quotes from an arbitrary on-chain liquidity provider. + /// @param providerAddress Address of the liquidity provider 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 sampleSellsFromLiquidityProvider( + address providerAddress, + address takerToken, + address makerToken, + uint256[] calldata takerTokenAmounts + ) + external + view + returns (uint256[] memory makerTokenAmounts); + + /// @dev Sample buy quotes from an arbitrary on-chain liquidity provider. + /// @param providerAddress Address of the liquidity provider 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 sampleBuysFromLiquidityProvider( + address providerAddress, + address takerToken, + address makerToken, + uint256[] calldata makerTokenAmounts + ) + external + view + returns (uint256[] memory takerTokenAmounts); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/ILiquidityProvider.sol b/contracts/erc20-bridge-sampler/contracts/src/ILiquidityProvider.sol new file mode 100644 index 0000000000..b88c23a251 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/ILiquidityProvider.sol @@ -0,0 +1,70 @@ +/* + + Copyright 2019 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.5.9; + + +interface ILiquidityProvider { + + /// @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 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 makerToken, + uint256 sellAmount + ) + external + view + returns (uint256 makerTokenAmount); + + /// @dev Quotes the amount of `takerToken` that would need to be sold in + /// order to obtain `buyAmount` of `makerToken`. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param buyAmount Amount of `makerToken` to buy. + /// @return takerTokenAmount Amount of `takerToken` that would need to be sold. + function getBuyQuote( + address takerToken, + address makerToken, + uint256 buyAmount + ) + external + view + returns (uint256 takerTokenAmount); +} diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json index a23392c52a..46906d6519 100644 --- a/contracts/erc20-bridge-sampler/package.json +++ b/contracts/erc20-bridge-sampler/package.json @@ -38,7 +38,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json" + "abis": "./test/generated-artifacts/@(ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|ILiquidityProvider|IUniswapExchangeQuotes|TestERC20BridgeSampler).json" }, "repository": { "type": "git", diff --git a/contracts/erc20-bridge-sampler/test/artifacts.ts b/contracts/erc20-bridge-sampler/test/artifacts.ts index d24ebaf458..6e9a15445c 100644 --- a/contracts/erc20-bridge-sampler/test/artifacts.ts +++ b/contracts/erc20-bridge-sampler/test/artifacts.ts @@ -11,6 +11,7 @@ import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json'; import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; +import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json'; import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; export const artifacts = { @@ -20,6 +21,7 @@ export const artifacts = { IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, + ILiquidityProvider: ILiquidityProvider as ContractArtifact, IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact, }; diff --git a/contracts/erc20-bridge-sampler/test/wrappers.ts b/contracts/erc20-bridge-sampler/test/wrappers.ts index 5c72640a1b..55db751e47 100644 --- a/contracts/erc20-bridge-sampler/test/wrappers.ts +++ b/contracts/erc20-bridge-sampler/test/wrappers.ts @@ -9,5 +9,6 @@ export * from '../test/generated-wrappers/i_dev_utils'; export * from '../test/generated-wrappers/i_erc20_bridge_sampler'; export * from '../test/generated-wrappers/i_eth2_dai'; export * from '../test/generated-wrappers/i_kyber_network'; +export * from '../test/generated-wrappers/i_liquidity_provider'; export * from '../test/generated-wrappers/i_uniswap_exchange_quotes'; export * from '../test/generated-wrappers/test_erc20_bridge_sampler'; diff --git a/contracts/erc20-bridge-sampler/tsconfig.json b/contracts/erc20-bridge-sampler/tsconfig.json index 9f3f4de903..a8cc5145ea 100644 --- a/contracts/erc20-bridge-sampler/tsconfig.json +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -11,6 +11,7 @@ "test/generated-artifacts/IERC20BridgeSampler.json", "test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IKyberNetwork.json", + "test/generated-artifacts/ILiquidityProvider.json", "test/generated-artifacts/IUniswapExchangeQuotes.json", "test/generated-artifacts/TestERC20BridgeSampler.json" ],