diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.json b/contracts/erc20-bridge-sampler/CHANGELOG.json index 4e044f6e95..619e31bd8b 100644 --- a/contracts/erc20-bridge-sampler/CHANGELOG.json +++ b/contracts/erc20-bridge-sampler/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Catch reverts to `DevUtils` calls", "pr": 2476 + }, + { + "note": "Remove wrapper functions and introduce `batchCall()`", + "pr": 2477 } ], "timestamp": 1581204851 diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 168976a058..357ac9d8e9 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -21,7 +21,6 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; -import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; @@ -36,154 +35,36 @@ contract ERC20BridgeSampler is IERC20BridgeSampler, DeploymentConstants { - bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); - uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3; - uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3; - uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3; - uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; - address constant private UNISWAP_SOURCE = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; - address constant private ETH2DAI_SOURCE = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; - address constant private KYBER_SOURCE = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; + /// @dev Gas limit for DevUtils calls. + uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k + /// @dev Gas limit for Kyber calls. + uint256 constant internal KYBER_CALL_GAS = 1500e3; // 1.5m + /// @dev Gas limit for Uniswap calls. + uint256 constant internal UNISWAP_CALL_GAS = 150e3; // 150k + /// @dev Base gas limit for Eth2Dai calls. + uint256 constant internal ETH2DAI_CALL_GAS = 1000e3; // 1m - /// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. - /// @param orders Batches of Native orders to query. - /// @param orderSignatures Batches of Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param takerTokenAmounts Batches of Taker token sell amount for each sample. - /// @return ordersAndSamples How much taker asset can be filled - /// by each order in `orders`. Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function queryBatchOrdersAndSampleSells( - LibOrder.Order[][] memory orders, - bytes[][] memory orderSignatures, - address[] memory sources, - uint256[][] memory takerTokenAmounts - ) - public + /// @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 view - returns ( - OrdersAndSample[] memory ordersAndSamples - ) + returns (bytes[] memory callResults) { - ordersAndSamples = new OrdersAndSample[](orders.length); - for (uint256 i = 0; i != orders.length; i++) { - ( - uint256[] memory orderFillableAssetAmounts, - uint256[][] memory tokenAmountsBySource - ) = queryOrdersAndSampleSells(orders[i], orderSignatures[i], sources, takerTokenAmounts[i]); - ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts; - ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource; + callResults = new bytes[](callDatas.length); + for (uint256 i = 0; i != callDatas.length; ++i) { + (bool didSucceed, bytes memory resultData) = address(this).staticcall(callDatas[i]); + if (!didSucceed) { + assembly { revert(add(resultData, 0x20), mload(resultData)) } + } + callResults[i] = resultData; } } - /// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once. - /// @param orders Batches of Native orders to query. - /// @param orderSignatures Batches of Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param makerTokenAmounts Batches of Maker token sell amount for each sample. - /// @return ordersAndSamples How much taker asset can be filled - /// by each order in `orders`. Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index. - function queryBatchOrdersAndSampleBuys( - LibOrder.Order[][] memory orders, - bytes[][] memory orderSignatures, - address[] memory sources, - uint256[][] memory makerTokenAmounts - ) - public - view - returns ( - OrdersAndSample[] memory ordersAndSamples - ) - { - ordersAndSamples = new OrdersAndSample[](orders.length); - for (uint256 i = 0; i != orders.length; i++) { - ( - uint256[] memory orderFillableAssetAmounts, - uint256[][] memory tokenAmountsBySource - ) = queryOrdersAndSampleBuys(orders[i], orderSignatures[i], sources, makerTokenAmounts[i]); - ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts; - ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource; - } - } - - /// @dev Query native orders and sample sell quotes on multiple DEXes at once. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return orderFillableTakerAssetAmounts How much taker asset can be filled - /// by each order in `orders`. - /// @return makerTokenAmountsBySource Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function queryOrdersAndSampleSells( - LibOrder.Order[] memory orders, - bytes[] memory orderSignatures, - address[] memory sources, - uint256[] memory takerTokenAmounts - ) - public - view - returns ( - uint256[] memory orderFillableTakerAssetAmounts, - uint256[][] memory makerTokenAmountsBySource - ) - { - require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS"); - orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts( - orders, - orderSignatures - ); - makerTokenAmountsBySource = sampleSells( - sources, - _assetDataToTokenAddress(orders[0].takerAssetData), - _assetDataToTokenAddress(orders[0].makerAssetData), - takerTokenAmounts - ); - } - - /// @dev Query native orders and sample buy quotes on multiple DEXes at once. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return orderFillableMakerAssetAmounts How much maker asset can be filled - /// by each order in `orders`. - /// @return takerTokenAmountsBySource Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index. - function queryOrdersAndSampleBuys( - LibOrder.Order[] memory orders, - bytes[] memory orderSignatures, - address[] memory sources, - uint256[] memory makerTokenAmounts - ) - public - view - returns ( - uint256[] memory orderFillableMakerAssetAmounts, - uint256[][] memory makerTokenAmountsBySource - ) - { - require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS"); - orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts( - orders, - orderSignatures - ); - makerTokenAmountsBySource = sampleBuys( - sources, - _assetDataToTokenAddress(orders[0].takerAssetData), - _assetDataToTokenAddress(orders[0].makerAssetData), - makerTokenAmounts - ); - } - /// @dev Queries the fillable taker asset amounts of native orders. /// Effectively ignores orders that have empty signatures or - /// maker/taker asset amounts (returning 0). + /// maker/taker asset amounts (returning 0). /// @param orders Native orders to query. /// @param orderSignatures Signatures for each respective order in `orders`. /// @return orderFillableTakerAssetAmounts How much taker asset can be filled @@ -270,66 +151,6 @@ contract ERC20BridgeSampler is } } - /// @dev Sample sell quotes on multiple DEXes at once. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @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 makerTokenAmountsBySource Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function sampleSells( - address[] memory sources, - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[][] memory makerTokenAmountsBySource) - { - uint256 numSources = sources.length; - makerTokenAmountsBySource = new uint256[][](numSources); - for (uint256 i = 0; i < numSources; i++) { - makerTokenAmountsBySource[i] = _sampleSellSource( - sources[i], - takerToken, - makerToken, - takerTokenAmounts - ); - } - } - - /// @dev Query native orders and sample buy quotes on multiple DEXes at once. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @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 takerTokenAmountsBySource Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index. - function sampleBuys( - address[] memory sources, - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts - ) - public - view - returns (uint256[][] memory takerTokenAmountsBySource) - { - uint256 numSources = sources.length; - takerTokenAmountsBySource = new uint256[][](numSources); - for (uint256 i = 0; i < numSources; i++) { - takerTokenAmountsBySource[i] = _sampleBuySource( - sources[i], - takerToken, - makerToken, - makerTokenAmounts - ); - } - } - /// @dev Sample sell quotes from Kyber. /// @param takerToken Address of the taker token (what to sell). /// @param makerToken Address of the maker token (what to buy). @@ -354,7 +175,7 @@ contract ERC20BridgeSampler is makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { (bool didSucceed, bytes memory resultData) = - _getKyberNetworkProxyAddress().staticcall.gas(KYBER_SAMPLE_CALL_GAS)( + _getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)( abi.encodeWithSelector( IKyberNetwork(0).getExpectedRate.selector, _takerToken, @@ -396,7 +217,7 @@ contract ERC20BridgeSampler is makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { (bool didSucceed, bytes memory resultData) = - _getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( + _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( abi.encodeWithSelector( IEth2Dai(0).getBuyAmount.selector, makerToken, @@ -433,7 +254,7 @@ contract ERC20BridgeSampler is takerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { (bool didSucceed, bytes memory resultData) = - _getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( + _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)( abi.encodeWithSelector( IEth2Dai(0).getPayAmount.selector, takerToken, @@ -599,7 +420,7 @@ contract ERC20BridgeSampler is } bytes memory resultData; (didSucceed, resultData) = - uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)( + uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)( abi.encodeWithSelector( functionSelector, inputAmount @@ -609,59 +430,6 @@ contract ERC20BridgeSampler is } } - /// @dev Samples a supported sell source, defined by its address. - /// @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 _sampleSellSource( - address source, - address takerToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - private - view - returns (uint256[] memory makerTokenAmounts) - { - if (source == ETH2DAI_SOURCE) { - return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts); - } - if (source == UNISWAP_SOURCE) { - return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts); - } - if (source == KYBER_SOURCE) { - return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); - } - revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE"); - } - - /// @dev Samples a supported buy source, defined by its address. - /// @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 _sampleBuySource( - address source, - address takerToken, - address makerToken, - uint256[] memory makerTokenAmounts - ) - private - view - returns (uint256[] memory takerTokenAmounts) - { - if (source == ETH2DAI_SOURCE) { - return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts); - } - if (source == UNISWAP_SOURCE) { - return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts); - } - revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE"); - } - /// @dev Retrive an existing Uniswap exchange contract. /// Throws if the exchange does not exist. /// @param tokenAddress Address of the token contract. @@ -677,23 +445,9 @@ contract ERC20BridgeSampler is ); } - /// @dev Extract the token address from ERC20 proxy asset data. - /// @param assetData ERC20 asset data. - /// @return tokenAddress The decoded token address. - function _assetDataToTokenAddress(bytes memory assetData) - private - pure - returns (address tokenAddress) - { - require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA"); - bytes4 selector; - assembly { - selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) - tokenAddress := mload(add(assetData, 0x24)) - } - require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY"); - } - + /// @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) private pure diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol index 42661de16d..a5b69047d0 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol @@ -23,98 +23,14 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; interface IERC20BridgeSampler { - struct OrdersAndSample { - uint256[] orderFillableAssetAmounts; - uint256[][] tokenAmountsBySource; - } - /// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. - /// @param orders Batches of Native orders to query. - /// @param orderSignatures Batches of Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param takerTokenAmounts Batches of Taker token sell amount for each sample. - /// @return ordersAndSamples How much taker asset can be filled - /// by each order in `orders`. Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function queryBatchOrdersAndSampleSells( - LibOrder.Order[][] calldata orders, - bytes[][] calldata orderSignatures, - address[] calldata sources, - uint256[][] calldata takerTokenAmounts - ) + /// @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 view - returns ( - OrdersAndSample[] memory ordersAndSamples - ); - - /// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once. - /// @param orders Batches of Native orders to query. - /// @param orderSignatures Batches of Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param makerTokenAmounts Batches of Maker token sell amount for each sample. - /// @return ordersAndSamples How much taker asset can be filled - /// by each order in `orders`. Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index - function queryBatchOrdersAndSampleBuys( - LibOrder.Order[][] calldata orders, - bytes[][] calldata orderSignatures, - address[] calldata sources, - uint256[][] calldata makerTokenAmounts - ) - external - view - returns ( - OrdersAndSample[] memory ordersAndSamples - ); - - /// @dev Query native orders and sample sell quotes on multiple DEXes at once. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return orderFillableTakerAssetAmounts How much taker asset can be filled - /// by each order in `orders`. - /// @return makerTokenAmountsBySource Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function queryOrdersAndSampleSells( - LibOrder.Order[] calldata orders, - bytes[] calldata orderSignatures, - address[] calldata sources, - uint256[] calldata takerTokenAmounts - ) - external - view - returns ( - uint256[] memory orderFillableTakerAssetAmounts, - uint256[][] memory makerTokenAmountsBySource - ); - - /// @dev Query native orders and sample buy quotes on multiple DEXes at once. - /// @param orders Native orders to query. - /// @param orderSignatures Signatures for each respective order in `orders`. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. - /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return orderFillableMakerAssetAmounts How much maker asset can be filled - /// by each order in `orders`. - /// @return takerTokenAmountsBySource Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index. - function queryOrdersAndSampleBuys( - LibOrder.Order[] calldata orders, - bytes[] calldata orderSignatures, - address[] calldata sources, - uint256[] calldata makerTokenAmounts - ) - external - view - returns ( - uint256[] memory orderFillableMakerAssetAmounts, - uint256[][] memory makerTokenAmountsBySource - ); + returns (bytes[] memory callResults); /// @dev Queries the fillable taker asset amounts of native orders. /// @param orders Native orders to query. @@ -142,39 +58,78 @@ interface IERC20BridgeSampler { view returns (uint256[] memory orderFillableMakerAssetAmounts); - /// @dev Sample sell quotes on multiple DEXes at once. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. + /// @dev Sample sell quotes from Kyber. /// @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 makerTokenAmountsBySource Maker amounts bought for each source at - /// each taker token amount. First indexed by source index, then sample - /// index. - function sampleSells( - address[] calldata sources, + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromKyberNetwork( address takerToken, address makerToken, uint256[] calldata takerTokenAmounts ) external view - returns (uint256[][] memory makerTokenAmountsBySource); + returns (uint256[] memory makerTokenAmounts); - /// @dev Query native orders and sample buy quotes on multiple DEXes at once. - /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. + /// @dev Sample sell quotes from Eth2Dai/Oasis. /// @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 takerTokenAmountsBySource Taker amounts sold for each source at - /// each maker token amount. First indexed by source index, then sample - /// index. - function sampleBuys( - address[] calldata sources, + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromEth2Dai( + address takerToken, + address makerToken, + uint256[] calldata takerTokenAmounts + ) + external + view + returns (uint256[] memory makerTokenAmounts); + + /// @dev Sample sell quotes from Uniswap. + /// @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 takerToken, + address makerToken, + uint256[] calldata takerTokenAmounts + ) + external + view + returns (uint256[] memory makerTokenAmounts); + + /// @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 takerToken, address makerToken, uint256[] calldata makerTokenAmounts ) external view - returns (uint256[][] memory takerTokenAmountsBySource); + returns (uint256[] memory takerTokenAmounts); + + /// @dev Sample buy quotes from Eth2Dai/Oasis. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Maker token sell amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromEth2Dai( + address takerToken, + address makerToken, + uint256[] calldata makerTokenAmounts + ) + external + view + returns (uint256[] memory takerTokenAmounts); } diff --git a/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol index 09ad84d197..06242d91b2 100644 --- a/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol @@ -327,7 +327,6 @@ contract TestERC20BridgeSampler is bytes memory ) public - view returns ( LibOrder.OrderInfo memory orderInfo, uint256 fillableTakerAssetAmount, diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index cf5d82a330..4c5efbd14a 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -25,19 +25,6 @@ blockchainTests('erc20-bridge-sampler', env => { const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7'; const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; const ERC20_PROXY_ID = '0xf47261b0'; - const INVALID_ASSET_PROXY_ASSET_DATA = hexUtils.concat('0xf47261b1', hexUtils.leftPad(randomAddress())); - const INVALID_ASSET_DATA = hexUtils.random(37); - const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap']; - const BUY_SOURCES = ['Eth2Dai', 'Uniswap']; - const SOURCE_IDS: { [source: string]: string } = { - Uniswap: '0xc0a47dfe034b400b47bdad5fecda2621de6c4d95', - Eth2Dai: '0x39755357759ce0d7f32dc8dc45414cca409ae24e', - Kyber: '0x818e6fecd516ecc3849daf6845e3ec868087b755', - }; - const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS'; - const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY'; - const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA'; - const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE'; const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR'; const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); @@ -324,329 +311,6 @@ blockchainTests('erc20-bridge-sampler', env => { }); }); - describe('queryOrdersAndSampleSells()', () => { - const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); - const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random()); - - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('returns expected fillable amounts for each order', async () => { - const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount); - const [orderInfos] = await testContract - .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => SOURCE_IDS[n]), takerTokenAmounts) - .callAsync(); - expect(orderInfos).to.deep.eq(expectedFillableAmounts); - }); - - it('can return quotes for all sources', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); - const [, quotes] = await testContract - .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => SOURCE_IDS[n]), sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('throws if no orders are passed in', async () => { - const tx = testContract - .queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => SOURCE_IDS[n]), getSampleAmounts(TAKER_TOKEN)) - .callAsync(); - return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); - }); - - it('throws with an unsupported source', async () => { - const tx = testContract - .queryOrdersAndSampleSells( - ORDERS, - SIGNATURES, - [...SELL_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()], - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - - it('throws with non-ERC20 maker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleSells( - ORDERS.map(o => ({ - ...o, - makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, - })), - SIGNATURES, - SELL_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); - }); - - it('throws with non-ERC20 taker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleSells( - ORDERS.map(o => ({ - ...o, - takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, - })), - SIGNATURES, - SELL_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); - }); - - it('throws with invalid maker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleSells( - ORDERS.map(o => ({ - ...o, - makerAssetData: INVALID_ASSET_DATA, - })), - SIGNATURES, - SELL_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); - }); - - it('throws with invalid taker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleSells( - ORDERS.map(o => ({ - ...o, - takerAssetData: INVALID_ASSET_DATA, - })), - SIGNATURES, - SELL_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); - }); - }); - - describe('queryOrdersAndSampleBuys()', () => { - const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); - const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random()); - - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('returns expected fillable amounts for each order', async () => { - const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount); - const [orderInfos] = await testContract - .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => SOURCE_IDS[n]), takerTokenAmounts) - .callAsync(); - expect(orderInfos).to.deep.eq(expectedFillableAmounts); - }); - - it('can return quotes for all sources', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); - const [, quotes] = await testContract - .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => SOURCE_IDS[n]), sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('throws if no orders are passed in', async () => { - const tx = testContract - .queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => SOURCE_IDS[n]), getSampleAmounts(MAKER_TOKEN)) - .callAsync(); - return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); - }); - - it('throws with an unsupported source', async () => { - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS, - SIGNATURES, - [...BUY_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()], - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - - it('throws if kyber is passed in as a source', async () => { - const sources = [...BUY_SOURCES, 'Kyber']; - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS, - SIGNATURES, - sources.map(n => SOURCE_IDS[n]), - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - - it('throws with non-ERC20 maker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS.map(o => ({ - ...o, - makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, - })), - SIGNATURES, - BUY_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); - }); - - it('throws with non-ERC20 taker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS.map(o => ({ - ...o, - takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, - })), - SIGNATURES, - BUY_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); - }); - - it('throws with invalid maker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS.map(o => ({ - ...o, - makerAssetData: INVALID_ASSET_DATA, - })), - SIGNATURES, - BUY_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); - }); - - it('throws with invalid taker asset data', async () => { - const tx = testContract - .queryOrdersAndSampleBuys( - ORDERS.map(o => ({ - ...o, - takerAssetData: INVALID_ASSET_DATA, - })), - SIGNATURES, - BUY_SOURCES.map(n => SOURCE_IDS[n]), - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); - }); - }); - - describe('sampleSells()', () => { - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('returns empty quotes with no sample amounts', async () => { - const emptyQuotes = _.times(SELL_SOURCES.length, () => []); - const quotes = await testContract - .sampleSells(SELL_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq(emptyQuotes); - }); - - it('can return quotes for all sources', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); - const quotes = await testContract - .sampleSells(SELL_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can return quotes for some sources', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const sources = _.sampleSize(SELL_SOURCES, 1); - const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts); - const quotes = await testContract - .sampleSells(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('throws with an unsupported source', async () => { - const tx = testContract - .sampleSells( - [...SELL_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()], - TAKER_TOKEN, - MAKER_TOKEN, - getSampleAmounts(TAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - }); - - describe('sampleBuys()', () => { - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('returns empty quotes with no sample amounts', async () => { - const emptyQuotes = _.times(BUY_SOURCES.length, () => []); - const quotes = await testContract - .sampleBuys(BUY_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq(emptyQuotes); - }); - - it('can return quotes for all sources', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); - const quotes = await testContract - .sampleBuys(BUY_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can return quotes for some sources', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const sources = _.sampleSize(BUY_SOURCES, 1); - const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts); - const quotes = await testContract - .sampleBuys(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('throws with an unsupported source', async () => { - const tx = testContract - .sampleBuys( - [...BUY_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()], - TAKER_TOKEN, - MAKER_TOKEN, - getSampleAmounts(MAKER_TOKEN), - ) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - - it('throws if kyber is passed in as a source', async () => { - const sources = [...BUY_SOURCES, 'Kyber']; - const tx = testContract - .sampleBuys(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(MAKER_TOKEN)) - .callAsync(); - return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); - }); - }); - blockchainTests.resets('sampleSellsFromKyberNetwork()', () => { before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); @@ -1051,4 +715,65 @@ blockchainTests('erc20-bridge-sampler', env => { expect(quotes).to.deep.eq(expectedQuotes); }); }); + + describe('batchCall()', () => { + it('can call one function', async () => { + const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); + const signatures: string[] = _.times(orders.length, i => hexUtils.random()); + const expected = orders.map(getDeterministicFillableTakerAssetAmount); + const calls = [ + testContract.getOrderFillableTakerAssetAmounts(orders, signatures).getABIEncodedTransactionData(), + ]; + const r = await testContract.batchCall(calls).callAsync(); + expect(r).to.be.length(1); + const actual = testContract.getABIDecodedReturnData('getOrderFillableTakerAssetAmounts', r[0]); + expect(actual).to.deep.eq(expected); + }); + + it('can call two functions', async () => { + const numOrders = _.random(1, 10); + const orders = _.times(2, () => createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders)); + const signatures: string[] = _.times(numOrders, i => hexUtils.random()); + const expecteds = [ + orders[0].map(getDeterministicFillableTakerAssetAmount), + orders[1].map(getDeterministicFillableMakerAssetAmount), + ]; + const calls = [ + testContract.getOrderFillableTakerAssetAmounts(orders[0], signatures).getABIEncodedTransactionData(), + testContract.getOrderFillableMakerAssetAmounts(orders[1], signatures).getABIEncodedTransactionData(), + ]; + const r = await testContract.batchCall(calls).callAsync(); + expect(r).to.be.length(2); + expect(testContract.getABIDecodedReturnData('getOrderFillableTakerAssetAmounts', r[0])).to.deep.eq( + expecteds[0], + ); + expect(testContract.getABIDecodedReturnData('getOrderFillableMakerAssetAmounts', r[1])).to.deep.eq( + expecteds[1], + ); + }); + + it('can make recursive calls', async () => { + const numOrders = _.random(1, 10); + const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders); + const signatures: string[] = _.times(numOrders, i => hexUtils.random()); + const expected = orders.map(getDeterministicFillableTakerAssetAmount); + let r = await testContract + .batchCall([ + testContract + .batchCall([ + testContract + .getOrderFillableTakerAssetAmounts(orders, signatures) + .getABIEncodedTransactionData(), + ]) + .getABIEncodedTransactionData(), + ]) + .callAsync(); + expect(r).to.be.length(1); + r = testContract.getABIDecodedReturnData('batchCall', r[0]); + expect(r).to.be.length(1); + expect(testContract.getABIDecodedReturnData('getOrderFillableTakerAssetAmounts', r[0])).to.deep.eq( + expected, + ); + }); + }); });