From 0af346aad8b691dad805c5b27e35e23046d10342 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 17 Oct 2019 20:42:53 -0400 Subject: [PATCH 01/14] `@0x/contracts-erc20-bridge-aggregator`: Create package. `@0x/contracts-erc20`: Add `decimals()` to `LibERC20Token`. `@0x/contracts-erc20-bridge-sampler`: Created package. --- contracts/erc20-bridge-sampler/.npmignore | 10 + contracts/erc20-bridge-sampler/CHANGELOG.json | 11 + contracts/erc20-bridge-sampler/CHANGELOG.md | 0 contracts/erc20-bridge-sampler/DEPLOYS.json | 17 + contracts/erc20-bridge-sampler/README.md | 3 + contracts/erc20-bridge-sampler/compiler.json | 26 ++ .../contracts/src/ERC20BridgeSampler.sol | 320 ++++++++++++++++++ .../contracts/src/IERC20BridgeSampler.sol | 48 +++ .../contracts/src/IEth2Dai.sol | 21 ++ .../contracts/src/IExchange.sol | 32 ++ .../contracts/src/IKyberNetwork.sol | 12 + .../contracts/src/IUniswapExchange.sol | 31 ++ .../contracts/src/LibERC20Token.sol | 98 ++++++ contracts/erc20-bridge-sampler/package.json | 91 +++++ contracts/erc20-bridge-sampler/src/.gitkeep | 0 contracts/erc20-bridge-sampler/test/.gitkeep | 0 .../erc20-bridge-sampler/truffle-config.js | 96 ++++++ contracts/erc20-bridge-sampler/tsconfig.json | 46 +++ contracts/erc20-bridge-sampler/tslint.json | 10 + contracts/erc20/CHANGELOG.json | 4 + .../erc20/contracts/src/LibERC20Token.sol | 16 + .../contracts/test/TestLibERC20Token.sol | 12 + .../test/TestLibERC20TokenTarget.sol | 8 + contracts/erc20/test/lib_erc20_token.ts | 88 +++-- 24 files changed, 972 insertions(+), 28 deletions(-) create mode 100644 contracts/erc20-bridge-sampler/.npmignore create mode 100644 contracts/erc20-bridge-sampler/CHANGELOG.json create mode 100644 contracts/erc20-bridge-sampler/CHANGELOG.md create mode 100644 contracts/erc20-bridge-sampler/DEPLOYS.json create mode 100644 contracts/erc20-bridge-sampler/README.md create mode 100644 contracts/erc20-bridge-sampler/compiler.json create mode 100644 contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/IExchange.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol create mode 100644 contracts/erc20-bridge-sampler/package.json create mode 100644 contracts/erc20-bridge-sampler/src/.gitkeep create mode 100644 contracts/erc20-bridge-sampler/test/.gitkeep create mode 100644 contracts/erc20-bridge-sampler/truffle-config.js create mode 100644 contracts/erc20-bridge-sampler/tsconfig.json create mode 100644 contracts/erc20-bridge-sampler/tslint.json diff --git a/contracts/erc20-bridge-sampler/.npmignore b/contracts/erc20-bridge-sampler/.npmignore new file mode 100644 index 0000000000..bdf2b8acbe --- /dev/null +++ b/contracts/erc20-bridge-sampler/.npmignore @@ -0,0 +1,10 @@ +# Blacklist all files +.* +* +# Whitelist lib +!lib/**/* +# Whitelist Solidity contracts +!contracts/src/**/* +# Blacklist tests in lib +/lib/test/* +# Package specific ignore diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.json b/contracts/erc20-bridge-sampler/CHANGELOG.json new file mode 100644 index 0000000000..c658665484 --- /dev/null +++ b/contracts/erc20-bridge-sampler/CHANGELOG.json @@ -0,0 +1,11 @@ +[ + { + "version": "1.0.0-beta.1", + "changes": [ + { + "note": "Created package.", + "pr": "TODO" + } + ] + } +] diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.md b/contracts/erc20-bridge-sampler/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contracts/erc20-bridge-sampler/DEPLOYS.json b/contracts/erc20-bridge-sampler/DEPLOYS.json new file mode 100644 index 0000000000..2eea680696 --- /dev/null +++ b/contracts/erc20-bridge-sampler/DEPLOYS.json @@ -0,0 +1,17 @@ +[ + { + "name": "Exchange", + "version": "2.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0xBDB58Fdf0a1587a0a258330a184Bb2C6cbA0aabe", + "3": "0x0000000000000000000000000000000000000000", + "4": "0x0000000000000000000000000000000000000000", + "42": "0x0000000000000000000000000000000000000000" + } + } + ] + } +] diff --git a/contracts/erc20-bridge-sampler/README.md b/contracts/erc20-bridge-sampler/README.md new file mode 100644 index 0000000000..7ab3cb2306 --- /dev/null +++ b/contracts/erc20-bridge-sampler/README.md @@ -0,0 +1,3 @@ +## ERC20BridgeSampler + +These are contracts used in DEX aggregation. diff --git a/contracts/erc20-bridge-sampler/compiler.json b/contracts/erc20-bridge-sampler/compiler.json new file mode 100644 index 0000000000..6d739870f0 --- /dev/null +++ b/contracts/erc20-bridge-sampler/compiler.json @@ -0,0 +1,26 @@ +{ + "artifactsDir": "./test/generated-artifacts", + "contractsDir": "./contracts", + "useDockerisedSolc": false, + "isOfflineMode": false, + "compilerSettings": { + "evmVersion": "constantinople", + "optimizer": { + "enabled": true, + "runs": 1000000, + "details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true } + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "devdoc", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol new file mode 100644 index 0000000000..c77e6237f6 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -0,0 +1,320 @@ +pragma solidity ^0.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; +import "./IERC20BridgeSampler.sol"; +import "./IExchange.sol"; +import "./IEth2Dai.sol"; +import "./IKyberNetwork.sol"; +import "./IUniswapExchange.sol"; + + +contract ERC20BridgeSampler is + IERC20BridgeSampler +{ + bytes4 constant internal ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)")); + address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712; // V2 + address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; + address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; + address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; + address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + function queryOrdersAndSampleSells( + IExchange.Order[] memory orders, + address[] memory sources, + uint256[] memory takerTokenAmounts + ) + public + view + returns ( + IExchange.OrderInfo[] memory orderInfos, + uint256[][] memory makerTokenAmountsBySource + ) + { + require(orders.length != 0, "EMPTY_ORDERS"); + orderInfos = queryOrders(orders); + makerTokenAmountsBySource = sampleSells( + sources, + _assetDataToTokenAddress(orders[0].takerAssetData), + _assetDataToTokenAddress(orders[0].makerAssetData), + takerTokenAmounts + ); + } + + function queryOrdersAndSampleBuys( + IExchange.Order[] memory orders, + address[] memory sources, + uint256[] memory makerTokenAmounts + ) + public + view + returns ( + IExchange.OrderInfo[] memory orderInfos, + uint256[][] memory makerTokenAmountsBySource + ) + { + orderInfos = queryOrders(orders); + makerTokenAmountsBySource = sampleBuys( + sources, + _assetDataToTokenAddress(orders[0].takerAssetData), + _assetDataToTokenAddress(orders[0].makerAssetData), + makerTokenAmounts + ); + } + + function queryOrders(IExchange.Order[] memory orders) + public + view + returns (IExchange.OrderInfo[] memory orderInfos) + { + uint256 numOrders = orders.length; + orderInfos = new IExchange.OrderInfo[](numOrders); + for (uint256 i = 0; i < numOrders; i++) { + orderInfos[i] = IExchange(EXCHANGE_ADDRESS).getOrderInfo(orders[i]); + } + } + + 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 + ); + } + } + + 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 + ); + } + } + + function sampleSellFromKyberNetwork( + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + address _takerToken = takerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : takerToken; + address _makerToken = makerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : makerToken; + uint256 takerTokenDecimals = LibERC20Token(takerToken).decimals(); + uint256 makerTokenDecimals = LibERC20Token(makerToken).decimals(); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + for (uint256 i = 0; i < numSamples; i++) { + (uint256 rate,) = IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS).getExpectedRate( + _takerToken, + _makerToken, + takerTokenAmounts[i] + ); + makerTokenAmounts[i] = + rate * + takerTokenAmounts[i] * + makerTokenDecimals / + (10 ** 18 * takerTokenDecimals); + } + } + + function sampleSellFromEth2Dai( + 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] = IEth2Dai(ETH2DAI_ADDRESS).getBuyAmount( + makerToken, + takerToken, + takerTokenAmounts[i] + ); + } + } + + function sampleBuyFromEth2Dai( + 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++) { + takerTokenAmounts[i] = IEth2Dai(ETH2DAI_ADDRESS).getPayAmount( + takerToken, + makerToken, + makerTokenAmounts[i] + ); + } + } + + function sampleSellFromUniswap( + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + IUniswapExchange takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchange(0) : + IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(takerToken); + IUniswapExchange makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchange(0) : + IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(makerToken); + for (uint256 i = 0; i < numSamples; i++) { + if (makerToken == WETH_ADDRESS) { + makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice( + takerTokenAmounts[i] + ); + } else if (takerToken == WETH_ADDRESS) { + makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( + takerTokenAmounts[i] + ); + } else { + uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice( + takerTokenAmounts[i] + ); + makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( + ethBought + ); + } + } + } + + function sampleBuyFromUniswap( + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + IUniswapExchange takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchange(0) : + IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(takerToken); + IUniswapExchange makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchange(0) : + IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(makerToken); + for (uint256 i = 0; i < numSamples; i++) { + if (makerToken == WETH_ADDRESS) { + takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( + makerTokenAmounts[i] + ); + } else if (takerToken == WETH_ADDRESS) { + takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice( + makerTokenAmounts[i] + ); + } else { + uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice( + makerTokenAmounts[i] + ); + takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( + ethSold + ); + } + } + } + + function _sampleSellSource( + address source, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + private + view + returns (uint256[] memory makerTokenAmounts) + { + if (source == ETH2DAI_ADDRESS) { + return sampleSellFromEth2Dai(takerToken, makerToken, takerTokenAmounts); + } + if (source == UNISWAP_EXCHANGE_FACTORY_ADDRESS) { + return sampleSellFromUniswap(takerToken, makerToken, takerTokenAmounts); + } + if (source == KYBER_NETWORK_PROXY_ADDRESS) { + return sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); + } + revert("UNSUPPORTED_SOURCE"); + } + + function _sampleBuySource( + address source, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + private + view + returns (uint256[] memory takerTokenAmounts) + { + if (source == ETH2DAI_ADDRESS) { + return sampleBuyFromEth2Dai(takerToken, makerToken, makerTokenAmounts); + } + if (source == UNISWAP_EXCHANGE_FACTORY_ADDRESS) { + return sampleBuyFromUniswap(takerToken, makerToken, makerTokenAmounts); + } + revert("UNSUPPORTED_SOURCE"); + } + + function _assetDataToTokenAddress(bytes memory assetData) + private + pure + returns (address tokenAddress) + { + require(assetData.length == 36, "INVALID_ASSET_DATA"); + bytes4 selector; + assembly { + selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + tokenAddress := mload(add(assetData, 0x24)) + } + require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY"); + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol new file mode 100644 index 0000000000..9186306815 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.5; +pragma experimental ABIEncoderV2; + +import "./IExchange.sol"; + + +interface IERC20BridgeSampler { + + /// @dev Query native orders and sample sell orders on multiple DEXes at once. + /// @param orders Native orders to query. + /// @param sources Address of each DEX. Passing in an unknown DEX will throw. + /// @param takerTokenAmounts Taker sell amount for each sample. + /// @return orderInfos `OrderInfo`s for 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( + IExchange.Order[] calldata orders, + address[] calldata sources, + uint256[] calldata takerTokenAmounts + ) + external + view + returns ( + IExchange.OrderInfo[] memory orderInfos, + uint256[][] memory makerTokenAmountsBySource + ); + + /// @dev Query native orders and sample buy orders on multiple DEXes at once. + /// @param orders Native orders to query. + /// @param sources Address of each DEX. Passing in an unknown DEX will throw. + /// @param makerTokenAmounts Maker sell amount for each sample. + /// @return orderInfos `OrderInfo`s for 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( + IExchange.Order[] calldata orders, + address[] calldata sources, + uint256[] calldata makerTokenAmounts + ) + external + view + returns ( + IExchange.OrderInfo[] memory orderInfos, + uint256[][] memory makerTokenAmountsBySource + ); +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol new file mode 100644 index 0000000000..c6d304e2ed --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.5; + +contract IEth2Dai { + function getBuyAmount( + address buyToken, + address payToken, + uint256 payAmount + ) + external + view + returns (uint256 buyAmount); + + function getPayAmount( + address payToken, + address buyToken, + uint256 buyAmount + ) + external + view + returns (uint256 payAmount); +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol b/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol new file mode 100644 index 0000000000..90c4e7e21e --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.5; +pragma experimental ABIEncoderV2; + +interface IExchange { + struct Order { + address makerAddress; + address takerAddress; + address feeRecipientAddress; + address senderAddress; + uint256 makerAssetAmount; + uint256 takerAssetAmount; + uint256 makerFee; + uint256 takerFee; + uint256 expirationTimeSeconds; + uint256 salt; + bytes makerAssetData; + bytes takerAssetData; + // bytes makerFeeAssetData; + // bytes takerFeeAssetData; + } + + struct OrderInfo { + uint8 orderStatus; + bytes32 orderHash; + uint256 orderTakerAssetFilledAmount; + } + + function getOrderInfo(Order calldata order) + external + view + returns (OrderInfo memory orderInfo); +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol new file mode 100644 index 0000000000..4f20469414 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.5; + +contract IKyberNetwork { + function getExpectedRate( + address fromToken, + address toToken, + uint256 fromAmount + ) + public + view + returns (uint256 expectedRate, uint256 slippageRate); +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol new file mode 100644 index 0000000000..61c38ca966 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.5; + +interface IUniswapExchange { + 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/erc20-bridge-sampler/contracts/src/LibERC20Token.sol b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol new file mode 100644 index 0000000000..d3c0b2e48b --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol @@ -0,0 +1,98 @@ +pragma solidity ^0.5; + + +library LibERC20Token { + + /// @dev Calls `IERC20Token(token).approve()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param spender The address that receives an allowance. + /// @param allowance The allowance to set. + function approve( + address token, + address spender, + uint256 allowance + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + 0x095ea7b3, + spender, + allowance + ); + _callWithOptionalBooleanResult(token, callData); + } + + /// @dev Calls `IERC20Token(token).transfer()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param to The address that receives the tokens + /// @param amount Number of tokens to transfer. + function transfer( + address token, + address to, + uint256 amount + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + 0xa9059cbb, + to, + amount + ); + _callWithOptionalBooleanResult(token, callData); + } + + /// @dev Calls `IERC20Token(token).transferFrom()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param from The owner of the tokens. + /// @param to The address that receives the tokens + /// @param amount Number of tokens to transfer. + function transferFrom( + address token, + address from, + address to, + uint256 amount + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + 0x23b872dd, + from, + to, + amount + ); + _callWithOptionalBooleanResult(token, callData); + } + + /// @dev Executes a call on address `target` with calldata `callData` + /// and asserts that either nothing was returned or a single boolean + /// was returned equal to `true`. + /// @param target The call target. + /// @param callData The abi-encoded call data. + function _callWithOptionalBooleanResult( + address target, + bytes memory callData + ) + private + { + (bool didSucceed, bytes memory resultData) = target.call(callData); + if (didSucceed) { + if (resultData.length == 0) { + return; + } + if (resultData.length == 32) { + uint256 result; + assembly { result := mload(add(resultData, 0x20)) } + if (result == 1) { + return; + } + } + } + assembly { revert(add(resultData, 0x20), mload(resultData)) } + } +} diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json new file mode 100644 index 0000000000..8880841cb0 --- /dev/null +++ b/contracts/erc20-bridge-sampler/package.json @@ -0,0 +1,91 @@ +{ + "name": "@0x/contracts-erc20-bridge-sampler", + "version": "1.0.0-beta.1", + "engines": { + "node": ">=6.12" + }, + "description": "Sampler contracts for the 0x asset-swapper", + "main": "lib/src/index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s compile contracts:gen generate_contract_wrappers contracts:copy", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", + "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "compile": "sol-compiler", + "watch": "sol-compiler -w", + "clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers", + "generate_contract_wrappers": "abi-gen --debug --abis ${npm_package_config_abis} --output test/generated-wrappers --backend ethers", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./test/generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude ./test/generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude ./test/generated-wrappers/**/* --exclude ./test/generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "coverage:report:text": "istanbul report text", + "coverage:report:html": "istanbul report html && open coverage/index.html", + "profiler:report:html": "istanbul report html && open coverage/index.html", + "coverage:report:lcov": "istanbul report lcov", + "test:circleci": "yarn test", + "contracts:gen": "contracts-gen generate", + "contracts:copy": "contracts-gen copy", + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol", + "compile:truffle": "truffle compile" + }, + "config": { + "publicInterfaceContracts": "ERC20BridgeSampler", + "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", + "abis": "./test/generated-artifacts/@(Exchange|IAssetProxy|IAssetProxyDispatcher|IEIP1271Data|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|IProtocolFees|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinProtocolFees|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestFillRounding|TestLibExchangeRichErrorDecoder|TestProtocolFeeCollector|TestProtocolFees|TestProtocolFeesReceiver|TestSignatureValidator|TestTransactions|TestValidatorWallet|TestWrapperFunctions).json" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", + "devDependencies": { + "@0x/abi-gen": "^4.4.0-beta.1", + "@0x/contracts-asset-proxy": "^2.3.0-beta.1", + "@0x/contracts-erc20": "^2.3.0-beta.1", + "@0x/contracts-exchange-libs": "^3.1.0-beta.1", + "@0x/contracts-gen": "^1.1.0-beta.1", + "@0x/contracts-test-utils": "^3.2.0-beta.1", + "@0x/contracts-utils": "^3.3.0-beta.1", + "@0x/dev-utils": "^2.4.0-beta.1", + "@0x/sol-compiler": "^3.2.0-beta.1", + "@0x/tslint-config": "^3.1.0-beta.1", + "@0x/web3-wrapper": "^6.1.0-beta.1", + "@types/lodash": "4.14.104", + "@types/mocha": "^5.2.7", + "@types/node": "*", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^3.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^6.2.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "solhint": "^1.4.1", + "truffle": "^5.0.32", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "dependencies": { + "@0x/base-contract": "^5.5.0-beta.1", + "@0x/types": "^2.5.0-beta.1", + "@0x/typescript-typings": "^4.4.0-beta.1", + "@0x/utils": "^4.6.0-beta.1", + "ethereum-types": "^2.2.0-beta.1", + "lodash": "^4.17.11" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/contracts/erc20-bridge-sampler/src/.gitkeep b/contracts/erc20-bridge-sampler/src/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contracts/erc20-bridge-sampler/test/.gitkeep b/contracts/erc20-bridge-sampler/test/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contracts/erc20-bridge-sampler/truffle-config.js b/contracts/erc20-bridge-sampler/truffle-config.js new file mode 100644 index 0000000000..2bcbbed1e2 --- /dev/null +++ b/contracts/erc20-bridge-sampler/truffle-config.js @@ -0,0 +1,96 @@ +/** + * Use this file to configure your truffle project. It's seeded with some + * common settings for different networks and features like migrations, + * compilation and testing. Uncomment the ones you need or modify + * them to suit your project as necessary. + * + * More information about configuration can be found at: + * + * truffleframework.com/docs/advanced/configuration + * + * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) + * to sign your transactions before they're sent to a remote public node. Infura accounts + * are available for free at: infura.io/register. + * + * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate + * public/private key pairs. If you're publishing your code to GitHub make sure you load this + * phrase from a file you've .gitignored so it doesn't accidentally become public. + * + */ + +// const HDWalletProvider = require('truffle-hdwallet-provider'); +// const infuraKey = "fj4jll3k....."; +// +// const fs = require('fs'); +// const mnemonic = fs.readFileSync(".secret").toString().trim(); + +module.exports = { + /** + * Networks define how you connect to your ethereum client and let you set the + * defaults web3 uses to send transactions. If you don't specify one truffle + * will spin up a development blockchain for you on port 9545 when you + * run `develop` or `test`. You can ask a truffle command to use a specific + * network from the command line, e.g + * + * $ truffle test --network + */ + + networks: { + // Useful for testing. The `development` name is special - truffle uses it by default + // if it's defined here and no other network is specified at the command line. + // You should run a client (like ganache-cli, geth or parity) in a separate terminal + // tab if you use this network and you must also set the `host`, `port` and `network_id` + // options below to some value. + // + // development: { + // host: "127.0.0.1", // Localhost (default: none) + // port: 8545, // Standard Ethereum port (default: none) + // network_id: "*", // Any network (default: none) + // }, + // Another network with more advanced options... + // advanced: { + // port: 8777, // Custom port + // network_id: 1342, // Custom network + // gas: 8500000, // Gas sent with each transaction (default: ~6700000) + // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) + // from:
, // Account to send txs from (default: accounts[0]) + // websockets: true // Enable EventEmitter interface for web3 (default: false) + // }, + // Useful for deploying to a public network. + // NB: It's important to wrap the provider as a function. + // ropsten: { + // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), + // network_id: 3, // Ropsten's id + // gas: 5500000, // Ropsten has a lower block limit than mainnet + // confirmations: 2, // # of confs to wait between deployments. (default: 0) + // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) + // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) + // }, + // Useful for private networks + // private: { + // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), + // network_id: 2111, // This network is yours, in the cloud. + // production: true // Treats this network as if it was a public net. (default: false) + // } + }, + + // Set default mocha options here, use special reporters etc. + mocha: { + // timeout: 100000 + }, + + // Configure your compilers + compilers: { + solc: { + version: '0.5.9', + settings: { + evmVersion: 'constantinople', + optimizer: { + enabled: true, + runs: 1000000, + details: { yul: true, deduplicate: true, cse: true, constantOptimizer: true }, + }, + }, + }, + }, +}; diff --git a/contracts/erc20-bridge-sampler/tsconfig.json b/contracts/erc20-bridge-sampler/tsconfig.json new file mode 100644 index 0000000000..b5598e3af5 --- /dev/null +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -0,0 +1,46 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, + "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "files": [ + "generated-artifacts/Exchange.json", + "generated-artifacts/IExchange.json", + "test/generated-artifacts/Exchange.json", + "test/generated-artifacts/IAssetProxy.json", + "test/generated-artifacts/IAssetProxyDispatcher.json", + "test/generated-artifacts/IEIP1271Data.json", + "test/generated-artifacts/IEIP1271Wallet.json", + "test/generated-artifacts/IExchange.json", + "test/generated-artifacts/IExchangeCore.json", + "test/generated-artifacts/IMatchOrders.json", + "test/generated-artifacts/IProtocolFees.json", + "test/generated-artifacts/ISignatureValidator.json", + "test/generated-artifacts/ITransactions.json", + "test/generated-artifacts/ITransferSimulator.json", + "test/generated-artifacts/IWallet.json", + "test/generated-artifacts/IWrapperFunctions.json", + "test/generated-artifacts/IsolatedExchange.json", + "test/generated-artifacts/LibExchangeRichErrorDecoder.json", + "test/generated-artifacts/MixinAssetProxyDispatcher.json", + "test/generated-artifacts/MixinExchangeCore.json", + "test/generated-artifacts/MixinMatchOrders.json", + "test/generated-artifacts/MixinProtocolFees.json", + "test/generated-artifacts/MixinSignatureValidator.json", + "test/generated-artifacts/MixinTransactions.json", + "test/generated-artifacts/MixinTransferSimulator.json", + "test/generated-artifacts/MixinWrapperFunctions.json", + "test/generated-artifacts/ReentrancyTester.json", + "test/generated-artifacts/TestAssetProxyDispatcher.json", + "test/generated-artifacts/TestExchangeInternals.json", + "test/generated-artifacts/TestFillRounding.json", + "test/generated-artifacts/TestLibExchangeRichErrorDecoder.json", + "test/generated-artifacts/TestProtocolFeeCollector.json", + "test/generated-artifacts/TestProtocolFees.json", + "test/generated-artifacts/TestProtocolFeesReceiver.json", + "test/generated-artifacts/TestSignatureValidator.json", + "test/generated-artifacts/TestTransactions.json", + "test/generated-artifacts/TestValidatorWallet.json", + "test/generated-artifacts/TestWrapperFunctions.json" + ], + "exclude": ["./deploy/solc/solc_bin"] +} diff --git a/contracts/erc20-bridge-sampler/tslint.json b/contracts/erc20-bridge-sampler/tslint.json new file mode 100644 index 0000000000..d45fc90e13 --- /dev/null +++ b/contracts/erc20-bridge-sampler/tslint.json @@ -0,0 +1,10 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "custom-no-magic-numbers": false, + "max-file-line-count": false + }, + "linterOptions": { + "exclude": ["src/artifacts.ts"] + } +} diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index bb96ba9e53..c9aa042288 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -14,6 +14,10 @@ { "note": "Drastically reduced bundle size by adding .npmignore, only exporting specific artifacts/wrappers/utils", "pr": 2330 + }, + { + "note": "Add `decimals()` to `LibERC20Token`.", + "pr": "TODO" } ], "timestamp": 1574030254 diff --git a/contracts/erc20/contracts/src/LibERC20Token.sol b/contracts/erc20/contracts/src/LibERC20Token.sol index e41a2875e8..830ef11f8f 100644 --- a/contracts/erc20/contracts/src/LibERC20Token.sol +++ b/contracts/erc20/contracts/src/LibERC20Token.sol @@ -24,6 +24,7 @@ import "../src/interfaces/IERC20Token.sol"; library LibERC20Token { + bytes constant private DECIMALS_CALL_DATA = hex"313ce567"; /// @dev Calls `IERC20Token(token).approve()`. /// Reverts if `false` is returned or if the return @@ -91,6 +92,21 @@ library LibERC20Token { _callWithOptionalBooleanResult(token, callData); } + /// @dev Retrieves the number of decimals for a token. + /// Returns `18` if the call reverts. + /// @return The number of decimals places for the token. + function decimals(address token) + internal + view + returns (uint8 tokenDecimals) + { + tokenDecimals = 18; + (bool didSucceed, bytes memory resultData) = token.staticcall(DECIMALS_CALL_DATA); + if (didSucceed && resultData.length == 32) { + tokenDecimals = uint8(LibBytes.readUint256(resultData, 0)); + } + } + /// @dev Executes a call on address `target` with calldata `callData` /// and asserts that either nothing was returned or a single boolean /// was returned equal to `true`. diff --git a/contracts/erc20/contracts/test/TestLibERC20Token.sol b/contracts/erc20/contracts/test/TestLibERC20Token.sol index 99fb8aed2f..bf64255209 100644 --- a/contracts/erc20/contracts/test/TestLibERC20Token.sol +++ b/contracts/erc20/contracts/test/TestLibERC20Token.sol @@ -69,4 +69,16 @@ contract TestLibERC20Token { target.setBehavior(shouldRevert, revertData, returnData); LibERC20Token.transferFrom(address(target), from, to, amount); } + + function testDecimals( + bool shouldRevert, + bytes calldata revertData, + bytes calldata returnData + ) + external + returns (uint8) + { + target.setBehavior(shouldRevert, revertData, returnData); + return LibERC20Token.decimals(address(target)); + } } diff --git a/contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol b/contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol index 82eaf04a74..b8a14f1001 100644 --- a/contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol +++ b/contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol @@ -87,6 +87,14 @@ contract TestLibERC20TokenTarget { _execute(); } + function decimals() + external + view + returns (uint8) + { + _execute(); + } + function _execute() private view { if (_shouldRevert) { bytes memory revertData = _revertData; diff --git a/contracts/erc20/test/lib_erc20_token.ts b/contracts/erc20/test/lib_erc20_token.ts index 2d864302c9..78b842a0bb 100644 --- a/contracts/erc20/test/lib_erc20_token.ts +++ b/contracts/erc20/test/lib_erc20_token.ts @@ -16,6 +16,7 @@ import { artifacts } from './artifacts'; blockchainTests('LibERC20Token', env => { let testContract: TestLibERC20TokenContract; const REVERT_STRING = 'WHOOPSIE'; + const ENCODED_REVERT = new StringRevertError(REVERT_STRING).encode(); const ENCODED_TRUE = hexLeftPad(1); const ENCODED_FALSE = hexLeftPad(0); const ENCODED_TWO = hexLeftPad(2); @@ -31,16 +32,12 @@ blockchainTests('LibERC20Token', env => { ); }); - function encodeRevert(message: string): string { - return new StringRevertError(message).encode(); - } - describe('approve()', () => { it('calls the target with the correct arguments', async () => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const { logs } = await testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance) .awaitTransactionSuccessAsync(); expect(logs).to.be.length(1); verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled); @@ -50,7 +47,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); await testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance) .awaitTransactionSuccessAsync(); }); @@ -58,7 +55,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); await testContract - .testApprove(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, spender, allowance) + .testApprove(false, ENCODED_REVERT, constants.NULL_BYTES, spender, allowance) .awaitTransactionSuccessAsync(); }); @@ -66,7 +63,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const tx = testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_FALSE, spender, allowance) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_FALSE); return expect(tx).to.revertWith(expectedError); @@ -76,7 +73,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const tx = testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TWO, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_TWO, spender, allowance) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_TWO); return expect(tx).to.revertWith(expectedError); @@ -86,7 +83,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const tx = testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_SHORT_TRUE, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, spender, allowance) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); return expect(tx).to.revertWith(expectedError); @@ -96,7 +93,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const tx = testContract - .testApprove(false, encodeRevert(REVERT_STRING), ENCODED_LONG_TRUE, spender, allowance) + .testApprove(false, ENCODED_REVERT, ENCODED_LONG_TRUE, spender, allowance) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_LONG_TRUE); return expect(tx).to.revertWith(expectedError); @@ -106,7 +103,7 @@ blockchainTests('LibERC20Token', env => { const spender = randomAddress(); const allowance = getRandomInteger(0, 100e18); const tx = testContract - .testApprove(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) + .testApprove(true, ENCODED_REVERT, ENCODED_TRUE, spender, allowance) .awaitTransactionSuccessAsync(); return expect(tx).to.revertWith(REVERT_STRING); }); @@ -126,7 +123,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const { logs } = await testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount) .awaitTransactionSuccessAsync(); expect(logs).to.be.length(1); verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled); @@ -136,7 +133,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); await testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount) .awaitTransactionSuccessAsync(); }); @@ -144,7 +141,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); await testContract - .testTransfer(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, to, amount) + .testTransfer(false, ENCODED_REVERT, constants.NULL_BYTES, to, amount) .awaitTransactionSuccessAsync(); }); @@ -152,7 +149,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_FALSE, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_FALSE); return expect(tx).to.revertWith(expectedError); @@ -162,7 +159,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TWO, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_TWO, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_TWO); return expect(tx).to.revertWith(expectedError); @@ -172,7 +169,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_SHORT_TRUE, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); return expect(tx).to.revertWith(expectedError); @@ -182,7 +179,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_LONG_TRUE, to, amount) + .testTransfer(false, ENCODED_REVERT, ENCODED_LONG_TRUE, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_LONG_TRUE); return expect(tx).to.revertWith(expectedError); @@ -192,7 +189,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransfer(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) + .testTransfer(true, ENCODED_REVERT, ENCODED_TRUE, to, amount) .awaitTransactionSuccessAsync(); return expect(tx).to.revertWith(REVERT_STRING); }); @@ -213,7 +210,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const { logs } = await testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount) .awaitTransactionSuccessAsync(); expect(logs).to.be.length(1); verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled); @@ -224,7 +221,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); await testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount) .awaitTransactionSuccessAsync(); }); @@ -233,7 +230,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); await testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, constants.NULL_BYTES, owner, to, amount) .awaitTransactionSuccessAsync(); }); @@ -242,7 +239,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_FALSE, owner, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_FALSE); return expect(tx).to.revertWith(expectedError); @@ -253,7 +250,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TWO, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_TWO, owner, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_TWO); return expect(tx).to.revertWith(expectedError); @@ -264,7 +261,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_SHORT_TRUE, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, owner, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); return expect(tx).to.revertWith(expectedError); @@ -275,7 +272,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_LONG_TRUE, owner, to, amount) + .testTransferFrom(false, ENCODED_REVERT, ENCODED_LONG_TRUE, owner, to, amount) .awaitTransactionSuccessAsync(); const expectedError = new RawRevertError(ENCODED_LONG_TRUE); return expect(tx).to.revertWith(expectedError); @@ -286,7 +283,7 @@ blockchainTests('LibERC20Token', env => { const to = randomAddress(); const amount = getRandomInteger(0, 100e18); const tx = testContract - .testTransferFrom(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) + .testTransferFrom(true, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount) .awaitTransactionSuccessAsync(); return expect(tx).to.revertWith(REVERT_STRING); }); @@ -301,4 +298,39 @@ blockchainTests('LibERC20Token', env => { return expect(tx).to.be.rejectedWith('revert'); }); }); + + describe('decimals()', () => { + const DEFAULT_DECIMALS = 18; + const ENCODED_ZERO = hexLeftPad(0); + const ENCODED_SHORT_ZERO = hexLeftPad(0, 31); + const ENCODED_LONG_ZERO = hexLeftPad(0, 33); + const randomDecimals = () => Math.floor(Math.random() * 256) + 1; + + it('returns the number of decimals defined by the token', async () => { + const decimals = randomDecimals(); + const encodedDecimals = hexLeftPad(decimals); + const result = await testContract.testDecimals(false, ENCODED_REVERT, encodedDecimals).callAsync(); + return expect(result).to.bignumber.eq(decimals); + }); + + it('returns 0 if the token returns 0', async () => { + const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_ZERO).callAsync(); + return expect(result).to.bignumber.eq(0); + }); + + it('returns 18 if the token returns less than 32 bytes', async () => { + const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_SHORT_ZERO).callAsync(); + return expect(result).to.bignumber.eq(DEFAULT_DECIMALS); + }); + + it('returns 18 if the token returns greater than 32 bytes', async () => { + const result = await testContract.testDecimals(false, ENCODED_REVERT, ENCODED_LONG_ZERO).callAsync(); + return expect(result).to.bignumber.eq(DEFAULT_DECIMALS); + }); + + it('returns 18 if the token reverts', async () => { + const result = await testContract.testDecimals(true, ENCODED_REVERT, ENCODED_ZERO).callAsync(); + return expect(result).to.bignumber.eq(DEFAULT_DECIMALS); + }); + }); }); From a26c3036a7478f64cccffc072c7348c5c92be262 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Fri, 15 Nov 2019 12:39:37 -0500 Subject: [PATCH 02/14] `@0x/contracts-erc20-bridge-sampler`: Get contracts compiling. --- .gitignore | 5 + .../contracts/src/ERC20BridgeSampler.sol | 126 +++++++++++++++--- .../contracts/src/IERC20BridgeSampler.sol | 46 ++++++- .../contracts/src/IEth2Dai.sol | 4 +- .../contracts/src/IExchange.sol | 4 +- .../contracts/src/IKyberNetwork.sol | 4 +- ...xchange.sol => IUniswapExchangeQuotes.sol} | 6 +- .../contracts/src/LibERC20Token.sol | 2 +- contracts/erc20-bridge-sampler/package.json | 4 +- .../erc20-bridge-sampler/src/artifacts.ts | 13 ++ .../erc20-bridge-sampler/src/wrappers.ts | 7 + .../erc20-bridge-sampler/test/artifacts.ts | 23 ++++ .../erc20-bridge-sampler/test/wrappers.ts | 12 ++ contracts/erc20-bridge-sampler/tsconfig.json | 45 ++----- contracts/erc20-bridge-sampler/tslint.json | 2 +- package.json | 2 +- 16 files changed, 241 insertions(+), 64 deletions(-) rename contracts/erc20-bridge-sampler/contracts/src/{IUniswapExchange.sol => IUniswapExchangeQuotes.sol} (90%) create mode 100644 contracts/erc20-bridge-sampler/src/artifacts.ts create mode 100644 contracts/erc20-bridge-sampler/src/wrappers.ts create mode 100644 contracts/erc20-bridge-sampler/test/artifacts.ts create mode 100644 contracts/erc20-bridge-sampler/test/wrappers.ts diff --git a/.gitignore b/.gitignore index 40c0275869..ec76ea0659 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,8 @@ TODO.md .vscode # generated contract artifacts/ +contracts/erc20-bridge-sampler/generated-artifacts/ +contracts/erc20-bridge-sampler/test/generated-artifacts/ contracts/integrations/generated-artifacts/ contracts/integrations/test/generated-artifacts/ contracts/staking/generated-artifacts/ @@ -111,6 +113,7 @@ packages/sol-tracing-utils/test/fixtures/artifacts/ python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/ # generated truffle contract artifacts/ +contracts/erc20-bridge-sampler/build/ contracts/staking/build/ contracts/coordinator/build/ contracts/exchange/build/ @@ -127,6 +130,8 @@ contracts/dev-utils/build/ # generated contract wrappers packages/python-contract-wrappers/generated/ +contracts/erc20-bridge-sampler/generated-wrappers/ +contracts/erc20-bridge-sampler/test/generated-wrappers/ contracts/integrations/generated-wrappers/ contracts/integrations/test/generated-wrappers/ contracts/staking/generated-wrappers/ diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index c77e6237f6..7ed04b5642 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; @@ -7,7 +7,7 @@ import "./IERC20BridgeSampler.sol"; import "./IExchange.sol"; import "./IEth2Dai.sol"; import "./IKyberNetwork.sol"; -import "./IUniswapExchange.sol"; +import "./IUniswapExchangeQuotes.sol"; contract ERC20BridgeSampler is @@ -21,6 +21,14 @@ contract ERC20BridgeSampler is address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev Query native orders and sample sell quotes on multiple DEXes at once. + /// @param orders Native orders to query. + /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return orderInfos `OrderInfo`s for 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( IExchange.Order[] memory orders, address[] memory sources, @@ -43,6 +51,14 @@ contract ERC20BridgeSampler is ); } + /// @dev Query native orders and sample buy quotes on multiple DEXes at once. + /// @param orders Native orders to query. + /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return orderInfos `OrderInfo`s for 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( IExchange.Order[] memory orders, address[] memory sources, @@ -64,6 +80,9 @@ contract ERC20BridgeSampler is ); } + /// @dev Queries the status of several native orders. + /// @param orders Native orders to query. + /// @return orderInfos Order info for each respective order. function queryOrders(IExchange.Order[] memory orders) public view @@ -76,6 +95,14 @@ 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, @@ -98,6 +125,14 @@ contract ERC20BridgeSampler is } } + /// @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, @@ -120,6 +155,12 @@ contract ERC20BridgeSampler is } } + /// @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 makerTokenAmounts Maker amounts bought at each taker token + /// amount. function sampleSellFromKyberNetwork( address takerToken, address makerToken, @@ -131,8 +172,8 @@ contract ERC20BridgeSampler is { address _takerToken = takerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : takerToken; address _makerToken = makerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : makerToken; - uint256 takerTokenDecimals = LibERC20Token(takerToken).decimals(); - uint256 makerTokenDecimals = LibERC20Token(makerToken).decimals(); + uint256 takerTokenDecimals = LibERC20Token.decimals(takerToken); + uint256 makerTokenDecimals = LibERC20Token.decimals(makerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { @@ -149,6 +190,12 @@ contract ERC20BridgeSampler is } } + /// @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 takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. function sampleSellFromEth2Dai( address takerToken, address makerToken, @@ -169,6 +216,12 @@ contract ERC20BridgeSampler is } } + /// @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 sampleBuyFromEth2Dai( address takerToken, address makerToken, @@ -189,6 +242,12 @@ contract ERC20BridgeSampler is } } + /// @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 sampleSellFromUniswap( address takerToken, address makerToken, @@ -200,12 +259,10 @@ contract ERC20BridgeSampler is { uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); - IUniswapExchange takerTokenExchange = takerToken == WETH_ADDRESS ? - IUniswapExchange(0) : - IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(takerToken); - IUniswapExchange makerTokenExchange = makerToken == WETH_ADDRESS ? - IUniswapExchange(0) : - IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(makerToken); + IUniswapExchangeQuotes takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); + IUniswapExchangeQuotes makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); for (uint256 i = 0; i < numSamples; i++) { if (makerToken == WETH_ADDRESS) { makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice( @@ -226,6 +283,12 @@ contract ERC20BridgeSampler is } } + /// @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 sampleBuyFromUniswap( address takerToken, address makerToken, @@ -237,12 +300,10 @@ contract ERC20BridgeSampler is { uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); - IUniswapExchange takerTokenExchange = takerToken == WETH_ADDRESS ? - IUniswapExchange(0) : - IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(takerToken); - IUniswapExchange makerTokenExchange = makerToken == WETH_ADDRESS ? - IUniswapExchange(0) : - IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS).getExchange(makerToken); + IUniswapExchangeQuotes takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); + IUniswapExchangeQuotes makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); for (uint256 i = 0; i < numSamples; i++) { if (makerToken == WETH_ADDRESS) { takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( @@ -263,6 +324,12 @@ 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, @@ -285,6 +352,12 @@ contract ERC20BridgeSampler is revert("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, @@ -304,6 +377,27 @@ contract ERC20BridgeSampler is revert("UNSUPPORTED_SOURCE"); } + /// @dev Retrive an existing Uniswap exchange contract. + /// Throws if the exchange does not exist. + /// @param tokenAddress Address of the token contract. + /// @return exchange `IUniswapExchangeQuotes` for the token. + function _getUniswapExchange(address tokenAddress) + private + view + returns (IUniswapExchangeQuotes exchange) + { + exchange = IUniswapExchangeQuotes( + address( + IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS) + .getExchange(tokenAddress) + ) + ); + require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE"); + } + + /// @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 diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol index 9186306815..1760a0e2bb 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; import "./IExchange.sol"; @@ -45,4 +45,48 @@ interface IERC20BridgeSampler { IExchange.OrderInfo[] memory orderInfos, uint256[][] memory makerTokenAmountsBySource ); + + /// @dev Queries the status of several native orders. + /// @param orders Native orders to query. + /// @return orderInfos Order info for each respective order. + function queryOrders(IExchange.Order[] calldata orders) + external + view + returns (IExchange.OrderInfo[] memory orderInfos); + + /// @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[] calldata sources, + address takerToken, + address makerToken, + uint256[] calldata takerTokenAmounts + ) + external + view + returns (uint256[][] memory makerTokenAmountsBySource); + + /// @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[] calldata sources, + address takerToken, + address makerToken, + uint256[] calldata makerTokenAmounts + ) + external + view + returns (uint256[][] memory takerTokenAmountsBySource); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol index c6d304e2ed..fbd96d89c3 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol @@ -1,6 +1,8 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; + contract IEth2Dai { + function getBuyAmount( address buyToken, address payToken, diff --git a/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol b/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol index 90c4e7e21e..b2bea43179 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol @@ -1,7 +1,9 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; + interface IExchange { + struct Order { address makerAddress; address takerAddress; diff --git a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol index 4f20469414..9420f982c9 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol @@ -1,6 +1,8 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; + contract IKyberNetwork { + function getExpectedRate( address fromToken, address toToken, diff --git a/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol similarity index 90% rename from contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol rename to contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol index 61c38ca966..1de760e89f 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchange.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol @@ -1,6 +1,8 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; + + +interface IUniswapExchangeQuotes { -interface IUniswapExchange { function getEthToTokenInputPrice( uint256 ethSold ) diff --git a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol index d3c0b2e48b..463dd1ea10 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5; +pragma solidity ^0.5.9; library LibERC20Token { diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json index 8880841cb0..435290db4e 100644 --- a/contracts/erc20-bridge-sampler/package.json +++ b/contracts/erc20-bridge-sampler/package.json @@ -36,9 +36,9 @@ "compile:truffle": "truffle compile" }, "config": { - "publicInterfaceContracts": "ERC20BridgeSampler", + "publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(Exchange|IAssetProxy|IAssetProxyDispatcher|IEIP1271Data|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|IProtocolFees|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinProtocolFees|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestFillRounding|TestLibExchangeRichErrorDecoder|TestProtocolFeeCollector|TestProtocolFees|TestProtocolFeesReceiver|TestSignatureValidator|TestTransactions|TestValidatorWallet|TestWrapperFunctions).json" + "abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IExchange|IKyberNetwork|IUniswapExchangeQuotes|LibERC20Token).json" }, "repository": { "type": "git", diff --git a/contracts/erc20-bridge-sampler/src/artifacts.ts b/contracts/erc20-bridge-sampler/src/artifacts.ts new file mode 100644 index 0000000000..41366d2e0e --- /dev/null +++ b/contracts/erc20-bridge-sampler/src/artifacts.ts @@ -0,0 +1,13 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +import { ContractArtifact } from 'ethereum-types'; + +import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json'; +import * as IERC20BridgeSampler from '../generated-artifacts/IERC20BridgeSampler.json'; +export const artifacts = { + ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, + IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, +}; diff --git a/contracts/erc20-bridge-sampler/src/wrappers.ts b/contracts/erc20-bridge-sampler/src/wrappers.ts new file mode 100644 index 0000000000..d1921d6176 --- /dev/null +++ b/contracts/erc20-bridge-sampler/src/wrappers.ts @@ -0,0 +1,7 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +export * from '../generated-wrappers/erc20_bridge_sampler'; +export * from '../generated-wrappers/i_erc20_bridge_sampler'; diff --git a/contracts/erc20-bridge-sampler/test/artifacts.ts b/contracts/erc20-bridge-sampler/test/artifacts.ts new file mode 100644 index 0000000000..8d964ab27d --- /dev/null +++ b/contracts/erc20-bridge-sampler/test/artifacts.ts @@ -0,0 +1,23 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +import { ContractArtifact } from 'ethereum-types'; + +import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; +import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; +import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; +import * as IExchange from '../test/generated-artifacts/IExchange.json'; +import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; +import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; +import * as LibERC20Token from '../test/generated-artifacts/LibERC20Token.json'; +export const artifacts = { + ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, + IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, + IEth2Dai: IEth2Dai as ContractArtifact, + IExchange: IExchange as ContractArtifact, + IKyberNetwork: IKyberNetwork as ContractArtifact, + IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, + LibERC20Token: LibERC20Token as ContractArtifact, +}; diff --git a/contracts/erc20-bridge-sampler/test/wrappers.ts b/contracts/erc20-bridge-sampler/test/wrappers.ts new file mode 100644 index 0000000000..6e52b706ae --- /dev/null +++ b/contracts/erc20-bridge-sampler/test/wrappers.ts @@ -0,0 +1,12 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +export * from '../test/generated-wrappers/erc20_bridge_sampler'; +export * from '../test/generated-wrappers/i_erc20_bridge_sampler'; +export * from '../test/generated-wrappers/i_eth2_dai'; +export * from '../test/generated-wrappers/i_exchange'; +export * from '../test/generated-wrappers/i_kyber_network'; +export * from '../test/generated-wrappers/i_uniswap_exchange_quotes'; +export * from '../test/generated-wrappers/lib_erc20_token'; diff --git a/contracts/erc20-bridge-sampler/tsconfig.json b/contracts/erc20-bridge-sampler/tsconfig.json index b5598e3af5..a668bb9f1c 100644 --- a/contracts/erc20-bridge-sampler/tsconfig.json +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -3,44 +3,15 @@ "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ - "generated-artifacts/Exchange.json", - "generated-artifacts/IExchange.json", - "test/generated-artifacts/Exchange.json", - "test/generated-artifacts/IAssetProxy.json", - "test/generated-artifacts/IAssetProxyDispatcher.json", - "test/generated-artifacts/IEIP1271Data.json", - "test/generated-artifacts/IEIP1271Wallet.json", + "generated-artifacts/ERC20BridgeSampler.json", + "generated-artifacts/IERC20BridgeSampler.json", + "test/generated-artifacts/ERC20BridgeSampler.json", + "test/generated-artifacts/IERC20BridgeSampler.json", + "test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IExchange.json", - "test/generated-artifacts/IExchangeCore.json", - "test/generated-artifacts/IMatchOrders.json", - "test/generated-artifacts/IProtocolFees.json", - "test/generated-artifacts/ISignatureValidator.json", - "test/generated-artifacts/ITransactions.json", - "test/generated-artifacts/ITransferSimulator.json", - "test/generated-artifacts/IWallet.json", - "test/generated-artifacts/IWrapperFunctions.json", - "test/generated-artifacts/IsolatedExchange.json", - "test/generated-artifacts/LibExchangeRichErrorDecoder.json", - "test/generated-artifacts/MixinAssetProxyDispatcher.json", - "test/generated-artifacts/MixinExchangeCore.json", - "test/generated-artifacts/MixinMatchOrders.json", - "test/generated-artifacts/MixinProtocolFees.json", - "test/generated-artifacts/MixinSignatureValidator.json", - "test/generated-artifacts/MixinTransactions.json", - "test/generated-artifacts/MixinTransferSimulator.json", - "test/generated-artifacts/MixinWrapperFunctions.json", - "test/generated-artifacts/ReentrancyTester.json", - "test/generated-artifacts/TestAssetProxyDispatcher.json", - "test/generated-artifacts/TestExchangeInternals.json", - "test/generated-artifacts/TestFillRounding.json", - "test/generated-artifacts/TestLibExchangeRichErrorDecoder.json", - "test/generated-artifacts/TestProtocolFeeCollector.json", - "test/generated-artifacts/TestProtocolFees.json", - "test/generated-artifacts/TestProtocolFeesReceiver.json", - "test/generated-artifacts/TestSignatureValidator.json", - "test/generated-artifacts/TestTransactions.json", - "test/generated-artifacts/TestValidatorWallet.json", - "test/generated-artifacts/TestWrapperFunctions.json" + "test/generated-artifacts/IKyberNetwork.json", + "test/generated-artifacts/IUniswapExchangeQuotes.json", + "test/generated-artifacts/LibERC20Token.json" ], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/contracts/erc20-bridge-sampler/tslint.json b/contracts/erc20-bridge-sampler/tslint.json index d45fc90e13..91bb1bec78 100644 --- a/contracts/erc20-bridge-sampler/tslint.json +++ b/contracts/erc20-bridge-sampler/tslint.json @@ -5,6 +5,6 @@ "max-file-line-count": false }, "linterOptions": { - "exclude": ["src/artifacts.ts"] + "exclude": ["src/artifacts.ts", "test/artifacts.ts"] } } diff --git a/package.json b/package.json index 62bd804b4c..54813ae215 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "lint:contracts": "wsrun lint -p ${npm_package_config_contractsPackages} -c --fast-exit --stages --exclude-missing" }, "config": { - "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-dev-utils @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-integrations @0x/contracts-multisig @0x/contracts-staking @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-coordinator @0x/contracts-tests", + "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-dev-utils @0x/contracts-erc20 @0x/contracts-erc20-bridge-sampler @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-integrations @0x/contracts-multisig @0x/contracts-staking @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-coordinator @0x/contracts-tests @0x/contracts-erc20-bridge-sampler", "mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic", "packagesWithDocPages": "0x.js @0x/contract-wrappers @0x/connect @0x/json-schemas @0x/subproviders @0x/web3-wrapper @0x/order-utils @0x/sol-compiler @0x/sol-coverage @0x/sol-profiler @0x/sol-trace @0x/dev-utils @0x/asset-swapper @0x/migrations @0x/orderbook @0x/contracts-asset-proxy @0x/contracts-coordinator @0x/contracts-dev-utils @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-extensions @0x/contracts-staking", "ignoreDependencyVersions": "@types/styled-components @types/node", From d02db3864e72fc4d845096af6685c13d99ea969e Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Fri, 15 Nov 2019 17:48:52 -0500 Subject: [PATCH 03/14] `@0x/contracts-erc20-bridge-sampler`: Fix kyber bug and add test contract. --- .../src/interfaces/IUniswapExchange.sol | 7 - .../contracts/src/DeploymentConstants.sol | 86 +++++ .../contracts/src/ERC20BridgeSampler.sol | 93 ++--- .../contracts/src/IERC20BridgeSampler.sol | 33 +- .../contracts/src/IEth2Dai.sol | 18 + .../contracts/src/IExchange.sol | 34 -- .../contracts/src/IKyberNetwork.sol | 18 + .../contracts/src/IUniswapExchangeQuotes.sol | 18 + .../contracts/src/LibERC20Token.sol | 18 + .../contracts/test/TestERC20BridgeSampler.sol | 320 ++++++++++++++++++ contracts/erc20-bridge-sampler/package.json | 3 +- .../erc20-bridge-sampler/test/artifacts.ts | 6 +- .../test/erc20-bridge-sampler.ts | 24 ++ .../erc20-bridge-sampler/test/wrappers.ts | 3 +- contracts/erc20-bridge-sampler/tsconfig.json | 5 +- 15 files changed, 592 insertions(+), 94 deletions(-) create mode 100644 contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol delete mode 100644 contracts/erc20-bridge-sampler/contracts/src/IExchange.sol create mode 100644 contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol create mode 100644 contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts diff --git a/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchange.sol b/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchange.sol index 20fa61ab58..7278da3978 100644 --- a/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchange.sol +++ b/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchange.sol @@ -67,11 +67,4 @@ interface IUniswapExchange { ) external returns (uint256 tokensBought); - - /// @dev Retrieves the token that is associated with this exchange. - /// @return tokenAddress The token address. - function toTokenAddress() - external - view - returns (address tokenAddress); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol b/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol new file mode 100644 index 0000000000..cc99e2e62c --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol @@ -0,0 +1,86 @@ +/* + + 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; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; +import "./IEth2Dai.sol"; +import "./IKyberNetwork.sol"; + + +contract DeploymentConstants { + + address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712; + address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; + address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; + address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; + address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /// @dev An overridable way to retrieve the 0x Exchange contract. + /// @return zeroex The 0x Exchange contract. + function _getExchangeContract() + internal + view + returns (IExchange zeroex) + { + return IExchange(EXCHANGE_ADDRESS); + } + + /// @dev An overridable way to retrieve the Eth2Dai exchange contract. + /// @return eth2dai The Eth2Dai exchange contract. + function _getEth2DaiContract() + internal + view + returns (IEth2Dai eth2dai) + { + return IEth2Dai(ETH2DAI_ADDRESS); + } + + /// @dev An overridable way to retrieve the Uniswap exchange factory contract. + /// @return uniswap The UniswapExchangeFactory contract. + function _getUniswapExchangeFactoryContract() + internal + view + returns (IUniswapExchangeFactory uniswap) + { + return IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS); + } + + /// @dev An overridable way to retrieve the Kyber network proxy contract. + /// @return kyber The KyberNeworkProxy contract. + function _getKyberNetworkContract() + internal + view + returns (IKyberNetwork kyber) + { + return IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS); + } + + /// @dev An overridable way to retrieve the WETH contract address. + /// @return weth The WETH contract address. + function _getWETHAddress() + internal + view + returns (address weth) + { + return WETH_ADDRESS; + } +} diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 7ed04b5642..897b32ff00 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -1,25 +1,40 @@ +/* + + 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; 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 "./IERC20BridgeSampler.sol"; -import "./IExchange.sol"; import "./IEth2Dai.sol"; import "./IKyberNetwork.sol"; import "./IUniswapExchangeQuotes.sol"; +import "./DeploymentConstants.sol"; contract ERC20BridgeSampler is - IERC20BridgeSampler + IERC20BridgeSampler, + DeploymentConstants { bytes4 constant internal ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)")); - address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712; // V2 - address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; - address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; - address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; - address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @dev Query native orders and sample sell quotes on multiple DEXes at once. /// @param orders Native orders to query. @@ -30,14 +45,14 @@ contract ERC20BridgeSampler is /// each taker token amount. First indexed by source index, then sample /// index. function queryOrdersAndSampleSells( - IExchange.Order[] memory orders, + LibOrder.Order[] memory orders, address[] memory sources, uint256[] memory takerTokenAmounts ) public view returns ( - IExchange.OrderInfo[] memory orderInfos, + LibOrder.OrderInfo[] memory orderInfos, uint256[][] memory makerTokenAmountsBySource ) { @@ -60,14 +75,14 @@ contract ERC20BridgeSampler is /// each maker token amount. First indexed by source index, then sample /// index. function queryOrdersAndSampleBuys( - IExchange.Order[] memory orders, + LibOrder.Order[] memory orders, address[] memory sources, uint256[] memory makerTokenAmounts ) public view returns ( - IExchange.OrderInfo[] memory orderInfos, + LibOrder.OrderInfo[] memory orderInfos, uint256[][] memory makerTokenAmountsBySource ) { @@ -83,15 +98,15 @@ contract ERC20BridgeSampler is /// @dev Queries the status of several native orders. /// @param orders Native orders to query. /// @return orderInfos Order info for each respective order. - function queryOrders(IExchange.Order[] memory orders) + function queryOrders(LibOrder.Order[] memory orders) public view - returns (IExchange.OrderInfo[] memory orderInfos) + returns (LibOrder.OrderInfo[] memory orderInfos) { uint256 numOrders = orders.length; - orderInfos = new IExchange.OrderInfo[](numOrders); + orderInfos = new LibOrder.OrderInfo[](numOrders); for (uint256 i = 0; i < numOrders; i++) { - orderInfos[i] = IExchange(EXCHANGE_ADDRESS).getOrderInfo(orders[i]); + orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]); } } @@ -170,14 +185,14 @@ contract ERC20BridgeSampler is view returns (uint256[] memory makerTokenAmounts) { - address _takerToken = takerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : takerToken; - address _makerToken = makerToken == WETH_ADDRESS ? KYBER_ETH_ADDRESS : makerToken; + address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken; + address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken; uint256 takerTokenDecimals = LibERC20Token.decimals(takerToken); uint256 makerTokenDecimals = LibERC20Token.decimals(makerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { - (uint256 rate,) = IKyberNetwork(KYBER_NETWORK_PROXY_ADDRESS).getExpectedRate( + (uint256 rate,) = _getKyberNetworkContract().getExpectedRate( _takerToken, _makerToken, takerTokenAmounts[i] @@ -185,8 +200,9 @@ contract ERC20BridgeSampler is makerTokenAmounts[i] = rate * takerTokenAmounts[i] * - makerTokenDecimals / - (10 ** 18 * takerTokenDecimals); + 10 ** makerTokenDecimals / + 10 ** takerTokenDecimals / + 10 ** 18; } } @@ -208,7 +224,7 @@ contract ERC20BridgeSampler is uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { - makerTokenAmounts[i] = IEth2Dai(ETH2DAI_ADDRESS).getBuyAmount( + makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount( makerToken, takerToken, takerTokenAmounts[i] @@ -234,7 +250,7 @@ contract ERC20BridgeSampler is uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { - takerTokenAmounts[i] = IEth2Dai(ETH2DAI_ADDRESS).getPayAmount( + takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount( takerToken, makerToken, makerTokenAmounts[i] @@ -259,16 +275,16 @@ contract ERC20BridgeSampler is { uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); - IUniswapExchangeQuotes takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); - IUniswapExchangeQuotes makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); for (uint256 i = 0; i < numSamples; i++) { - if (makerToken == WETH_ADDRESS) { + if (makerToken == _getWETHAddress()) { makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice( takerTokenAmounts[i] ); - } else if (takerToken == WETH_ADDRESS) { + } else if (takerToken == _getWETHAddress()) { makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( takerTokenAmounts[i] ); @@ -300,16 +316,16 @@ contract ERC20BridgeSampler is { uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); - IUniswapExchangeQuotes takerTokenExchange = takerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); - IUniswapExchangeQuotes makerTokenExchange = makerToken == WETH_ADDRESS ? + IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); for (uint256 i = 0; i < numSamples; i++) { - if (makerToken == WETH_ADDRESS) { + if (makerToken == _getWETHAddress()) { takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( makerTokenAmounts[i] ); - } else if (takerToken == WETH_ADDRESS) { + } else if (takerToken == _getWETHAddress()) { takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice( makerTokenAmounts[i] ); @@ -340,13 +356,13 @@ contract ERC20BridgeSampler is view returns (uint256[] memory makerTokenAmounts) { - if (source == ETH2DAI_ADDRESS) { + if (source == address(_getEth2DaiContract())) { return sampleSellFromEth2Dai(takerToken, makerToken, takerTokenAmounts); } - if (source == UNISWAP_EXCHANGE_FACTORY_ADDRESS) { + if (source == address(_getUniswapExchangeFactoryContract())) { return sampleSellFromUniswap(takerToken, makerToken, takerTokenAmounts); } - if (source == KYBER_NETWORK_PROXY_ADDRESS) { + if (source == address(_getKyberNetworkContract())) { return sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); } revert("UNSUPPORTED_SOURCE"); @@ -368,10 +384,10 @@ contract ERC20BridgeSampler is view returns (uint256[] memory takerTokenAmounts) { - if (source == ETH2DAI_ADDRESS) { + if (source == address(_getEth2DaiContract())) { return sampleBuyFromEth2Dai(takerToken, makerToken, makerTokenAmounts); } - if (source == UNISWAP_EXCHANGE_FACTORY_ADDRESS) { + if (source == address(_getUniswapExchangeFactoryContract())) { return sampleBuyFromUniswap(takerToken, makerToken, makerTokenAmounts); } revert("UNSUPPORTED_SOURCE"); @@ -387,10 +403,7 @@ contract ERC20BridgeSampler is returns (IUniswapExchangeQuotes exchange) { exchange = IUniswapExchangeQuotes( - address( - IUniswapExchangeFactory(UNISWAP_EXCHANGE_FACTORY_ADDRESS) - .getExchange(tokenAddress) - ) + address(_getUniswapExchangeFactoryContract().getExchange(tokenAddress)) ); require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE"); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol index 1760a0e2bb..bdbc92d393 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IERC20BridgeSampler.sol @@ -1,7 +1,26 @@ +/* + + 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; pragma experimental ABIEncoderV2; -import "./IExchange.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; interface IERC20BridgeSampler { @@ -15,14 +34,14 @@ interface IERC20BridgeSampler { /// each taker token amount. First indexed by source index, then sample /// index. function queryOrdersAndSampleSells( - IExchange.Order[] calldata orders, + LibOrder.Order[] calldata orders, address[] calldata sources, uint256[] calldata takerTokenAmounts ) external view returns ( - IExchange.OrderInfo[] memory orderInfos, + LibOrder.OrderInfo[] memory orderInfos, uint256[][] memory makerTokenAmountsBySource ); @@ -35,24 +54,24 @@ interface IERC20BridgeSampler { /// each maker token amount. First indexed by source index, then sample /// index. function queryOrdersAndSampleBuys( - IExchange.Order[] calldata orders, + LibOrder.Order[] calldata orders, address[] calldata sources, uint256[] calldata makerTokenAmounts ) external view returns ( - IExchange.OrderInfo[] memory orderInfos, + LibOrder.OrderInfo[] memory orderInfos, uint256[][] memory makerTokenAmountsBySource ); /// @dev Queries the status of several native orders. /// @param orders Native orders to query. /// @return orderInfos Order info for each respective order. - function queryOrders(IExchange.Order[] calldata orders) + function queryOrders(LibOrder.Order[] calldata orders) external view - returns (IExchange.OrderInfo[] memory orderInfos); + returns (LibOrder.OrderInfo[] memory orderInfos); /// @dev Sample sell quotes on multiple DEXes at once. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw. diff --git a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol index fbd96d89c3..ea68aca77f 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol @@ -1,3 +1,21 @@ +/* + + 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; diff --git a/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol b/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol deleted file mode 100644 index b2bea43179..0000000000 --- a/contracts/erc20-bridge-sampler/contracts/src/IExchange.sol +++ /dev/null @@ -1,34 +0,0 @@ -pragma solidity ^0.5.9; -pragma experimental ABIEncoderV2; - - -interface IExchange { - - struct Order { - address makerAddress; - address takerAddress; - address feeRecipientAddress; - address senderAddress; - uint256 makerAssetAmount; - uint256 takerAssetAmount; - uint256 makerFee; - uint256 takerFee; - uint256 expirationTimeSeconds; - uint256 salt; - bytes makerAssetData; - bytes takerAssetData; - // bytes makerFeeAssetData; - // bytes takerFeeAssetData; - } - - struct OrderInfo { - uint8 orderStatus; - bytes32 orderHash; - uint256 orderTakerAssetFilledAmount; - } - - function getOrderInfo(Order calldata order) - external - view - returns (OrderInfo memory orderInfo); -} diff --git a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol index 9420f982c9..4dc8ca134d 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol @@ -1,3 +1,21 @@ +/* + + 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; diff --git a/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol index 1de760e89f..2c0dc600a4 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IUniswapExchangeQuotes.sol @@ -1,3 +1,21 @@ +/* + + 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; diff --git a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol index 463dd1ea10..97df57b0d9 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol @@ -1,3 +1,21 @@ +/* + + 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; diff --git a/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol new file mode 100644 index 0000000000..df9001cd69 --- /dev/null +++ b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol @@ -0,0 +1,320 @@ +/* + + 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; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "../src/ERC20BridgeSampler.sol"; +import "../src/IEth2Dai.sol"; +import "../src/IKyberNetwork.sol"; + + +library LibDeterministicQuotes { + + uint256 private constant MAX_RATE = 10 ** 20; + uint256 private constant MIN_RATE = 10 ** 16; + + function getDeterministicSellQuote( + bytes32 salt, + address sellToken, + address buyToken, + uint256 sellAmount + ) + internal + pure + returns (uint256 buyAmount) + { + uint256 sellBase = 10 ** getDeterministicTokenDecimals(sellToken); + uint256 buyBase = 10 ** getDeterministicTokenDecimals(buyToken); + uint256 rate = _getDeterministicRate(salt, sellToken, buyToken); + return sellAmount * rate * buyBase / sellBase / 10 ** 18; + } + + function getDeterministicBuyQuote( + bytes32 salt, + address sellToken, + address buyToken, + uint256 buyAmount + ) + internal + pure + returns (uint256 sellAmount) + { + uint256 sellBase = 10 ** getDeterministicTokenDecimals(sellToken); + uint256 buyBase = 10 ** getDeterministicTokenDecimals(buyToken); + uint256 rate = _getDeterministicRate(salt, sellToken, buyToken); + return buyAmount * 10 ** 18 * sellBase / rate / buyBase; + } + + function getDeterministicTokenDecimals(address token) + internal + pure + returns (uint8 decimals) + { + bytes32 seed = keccak256(abi.encodePacked(token)); + return uint8(uint256(seed) % 18) + 6; + } + + function _getDeterministicRate(bytes32 salt, address sellToken, address buyToken) + private + pure + returns (uint256 rate) + { + bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken)); + return MIN_RATE + uint256(seed) % MAX_RATE; + } +} + + +contract TestERC20BridgeSamplerUniswapExchange is + IUniswapExchangeQuotes +{ + bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab; + + address public tokenAddress; + bytes32 public salt; + address private _wethAddress; + + constructor(address _tokenAddress) public { + _wethAddress = msg.sender; + tokenAddress = _tokenAddress; + salt = keccak256(abi.encodePacked(BASE_SALT, _tokenAddress)); + } + + // Deterministic `IUniswapExchangeQuotes.getEthToTokenInputPrice()`. + function getEthToTokenInputPrice( + uint256 ethSold + ) + external + view + returns (uint256 tokensBought) + { + return LibDeterministicQuotes.getDeterministicSellQuote( + salt, + _wethAddress, + tokenAddress, + ethSold + ); + } + + // Deterministic `IUniswapExchangeQuotes.getEthToTokenOutputPrice()`. + function getEthToTokenOutputPrice( + uint256 tokensBought + ) + external + view + returns (uint256 ethSold) + { + return LibDeterministicQuotes.getDeterministicBuyQuote( + salt, + _wethAddress, + tokenAddress, + tokensBought + ); + } + + // Deterministic `IUniswapExchangeQuotes.getTokenToEthInputPrice()`. + function getTokenToEthInputPrice( + uint256 tokensSold + ) + external + view + returns (uint256 ethBought) + { + return LibDeterministicQuotes.getDeterministicSellQuote( + salt, + tokenAddress, + _wethAddress, + tokensSold + ); + } + + // Deterministic `IUniswapExchangeQuotes.getTokenToEthOutputPrice()`. + function getTokenToEthOutputPrice( + uint256 ethBought + ) + external + view + returns (uint256 tokensSold) + { + return LibDeterministicQuotes.getDeterministicBuyQuote( + salt, + tokenAddress, + _wethAddress, + ethBought + ); + } +} + + +contract TestERC20BridgeSampler is + ERC20BridgeSampler +{ + bytes32 constant private KYBER_SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; + bytes32 constant private ETH2DAI_SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7; + + mapping (address => IUniswapExchangeQuotes) private _uniswapExchangesByToken; + + // Creates Uniswap exchange contracts for tokens. + function createTokenExchanges(address[] calldata tokenAddresses) + external + { + for (uint256 i = 0; i < tokenAddresses.length; i++) { + address tokenAddress = tokenAddresses[i]; + _uniswapExchangesByToken[tokenAddress] = + new TestERC20BridgeSamplerUniswapExchange(tokenAddress); + } + } + + // `IERC20.decimals()` (for WETH). + function decimals() + external + view + returns (uint8 decimalPlaces) + { + return 18; + } + + // Deterministic `IKyberNetwork.getExpectedRate()`. + function getExpectedRate( + address fromToken, + address toToken, + uint256 fromAmount + ) + external + view + returns (uint256 expectedRate, uint256 slippageRate) + { + uint256 quote = LibDeterministicQuotes.getDeterministicSellQuote( + KYBER_SALT, + fromToken, + toToken, + fromAmount + ); + expectedRate = + 10 ** 18 * + (quote / LibDeterministicQuotes.getDeterministicTokenDecimals(toToken)) / + (fromAmount * LibDeterministicQuotes.getDeterministicTokenDecimals(fromToken)); + } + + // Deterministic `IEth2Dai.getBuyAmount()`. + function getBuyAmount( + address buyToken, + address payToken, + uint256 payAmount + ) + external + view + returns (uint256 buyAmount) + { + return LibDeterministicQuotes.getDeterministicSellQuote( + ETH2DAI_SALT, + payToken, + buyToken, + payAmount + ); + } + + // Deterministic `IEth2Dai.getPayAmount()`. + function getPayAmount( + address payToken, + address buyToken, + uint256 buyAmount + ) + external + view + returns (uint256 payAmount) + { + return LibDeterministicQuotes.getDeterministicBuyQuote( + ETH2DAI_SALT, + payToken, + buyToken, + buyAmount + ); + } + + // `IUniswapExchangeFactory.getExchange()`. + function getExchange(address tokenAddress) + external + view + returns (address) + { + return address(_uniswapExchangesByToken[tokenAddress]); + } + + // `IExchange.getOrderInfo()`, overridden to return deterministic order infos. + function getOrderInfo(LibOrder.Order memory order) + public + view + returns (LibOrder.OrderInfo memory orderInfo) + { + // The order hash is just the hash of the salt. + bytes32 orderHash = keccak256(abi.encode(order.salt)); + // Everything else is derived from the hash. + orderInfo.orderHash = orderHash; + orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1)); + orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount; + } + + // Overriden to point to this contract. + function _getExchangeContract() + internal + view + returns (IExchange zeroex) + { + return IExchange(address(this)); + } + + // Overriden to point to this contract. + function _getEth2DaiContract() + internal + view + returns (IEth2Dai eth2dai) + { + return IEth2Dai(address(this)); + } + + // Overriden to point to this contract. + function _getUniswapExchangeFactoryContract() + internal + view + returns (IUniswapExchangeFactory uniswap) + { + return IUniswapExchangeFactory(address(this)); + } + + // Overriden to point to this contract. + function _getKyberNetworkContract() + internal + view + returns (IKyberNetwork kyber) + { + return IKyberNetwork(address(this)); + } + + // Overriden to point to this contract. + function _getWETHAddress() + internal + view + returns (address weth) + { + return address(this); + } +} diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json index 435290db4e..b96ed8b7b8 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|IERC20BridgeSampler|IEth2Dai|IExchange|IKyberNetwork|IUniswapExchangeQuotes|LibERC20Token).json" + "abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|LibERC20Token|TestERC20BridgeSampler).json" }, "repository": { "type": "git", @@ -54,6 +54,7 @@ "@0x/contracts-asset-proxy": "^2.3.0-beta.1", "@0x/contracts-erc20": "^2.3.0-beta.1", "@0x/contracts-exchange-libs": "^3.1.0-beta.1", + "@0x/contracts-exchange": "^2.2.0-beta.1", "@0x/contracts-gen": "^1.1.0-beta.1", "@0x/contracts-test-utils": "^3.2.0-beta.1", "@0x/contracts-utils": "^3.3.0-beta.1", diff --git a/contracts/erc20-bridge-sampler/test/artifacts.ts b/contracts/erc20-bridge-sampler/test/artifacts.ts index 8d964ab27d..388c59b542 100644 --- a/contracts/erc20-bridge-sampler/test/artifacts.ts +++ b/contracts/erc20-bridge-sampler/test/artifacts.ts @@ -5,19 +5,21 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json'; import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; -import * as IExchange from '../test/generated-artifacts/IExchange.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; import * as LibERC20Token from '../test/generated-artifacts/LibERC20Token.json'; +import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; export const artifacts = { + DeploymentConstants: DeploymentConstants as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact, - IExchange: IExchange as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, LibERC20Token: LibERC20Token as ContractArtifact, + TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact, }; diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts new file mode 100644 index 0000000000..33740967a7 --- /dev/null +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -0,0 +1,24 @@ +import { blockchainTests, expect, getRandomInteger } from '@0x/contracts-test-utils'; +import { BigNumber } from '@0x/utils'; + +import { artifacts } from './artifacts'; +import { TestERC20BridgeSamplerContract } from './wrappers'; + +blockchainTests('erc20-bridge-sampler', env => { + let testContract: TestERC20BridgeSamplerContract; + + before(async () => { + testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( + artifacts.TestERC20BridgeSampler, + env.provider, + env.txDefaults, + {}, + ); + }); + + describe('queryOrders()', () => { + it('returns the results of `getOrderInfo()` for each order', async () => { + + }); + }); +}); diff --git a/contracts/erc20-bridge-sampler/test/wrappers.ts b/contracts/erc20-bridge-sampler/test/wrappers.ts index 6e52b706ae..c4980e30fd 100644 --- a/contracts/erc20-bridge-sampler/test/wrappers.ts +++ b/contracts/erc20-bridge-sampler/test/wrappers.ts @@ -3,10 +3,11 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../test/generated-wrappers/deployment_constants'; export * from '../test/generated-wrappers/erc20_bridge_sampler'; export * from '../test/generated-wrappers/i_erc20_bridge_sampler'; export * from '../test/generated-wrappers/i_eth2_dai'; -export * from '../test/generated-wrappers/i_exchange'; export * from '../test/generated-wrappers/i_kyber_network'; export * from '../test/generated-wrappers/i_uniswap_exchange_quotes'; export * from '../test/generated-wrappers/lib_erc20_token'; +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 a668bb9f1c..d5a7f69ea0 100644 --- a/contracts/erc20-bridge-sampler/tsconfig.json +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -5,13 +5,14 @@ "files": [ "generated-artifacts/ERC20BridgeSampler.json", "generated-artifacts/IERC20BridgeSampler.json", + "test/generated-artifacts/DeploymentConstants.json", "test/generated-artifacts/ERC20BridgeSampler.json", "test/generated-artifacts/IERC20BridgeSampler.json", "test/generated-artifacts/IEth2Dai.json", - "test/generated-artifacts/IExchange.json", "test/generated-artifacts/IKyberNetwork.json", "test/generated-artifacts/IUniswapExchangeQuotes.json", - "test/generated-artifacts/LibERC20Token.json" + "test/generated-artifacts/LibERC20Token.json", + "test/generated-artifacts/TestERC20BridgeSampler.json" ], "exclude": ["./deploy/solc/solc_bin"] } From 27fb51d37ff9f7db7c7deba4e0d3734bfb73c256 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 00:26:01 -0500 Subject: [PATCH 04/14] `@0x/contracts-asset-proxy`: Tweak `IUniswapExchangeFactory`. --- contracts/asset-proxy/contracts/src/bridges/UniswapBridge.sol | 4 +++- .../contracts/src/interfaces/IUniswapExchangeFactory.sol | 2 +- contracts/asset-proxy/contracts/test/TestUniswapBridge.sol | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/asset-proxy/contracts/src/bridges/UniswapBridge.sol b/contracts/asset-proxy/contracts/src/bridges/UniswapBridge.sol index 6e6d325522..23fb69c47c 100644 --- a/contracts/asset-proxy/contracts/src/bridges/UniswapBridge.sol +++ b/contracts/asset-proxy/contracts/src/bridges/UniswapBridge.sol @@ -210,7 +210,9 @@ contract UniswapBridge is if (fromTokenAddress == address(getWethContract())) { exchangeTokenAddress = toTokenAddress; } - exchange = getUniswapExchangeFactoryContract().getExchange(exchangeTokenAddress); + exchange = IUniswapExchange( + getUniswapExchangeFactoryContract().getExchange(exchangeTokenAddress) + ); require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN"); return exchange; } diff --git a/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol b/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol index c175f55326..d91c5319d5 100644 --- a/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol +++ b/contracts/asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol @@ -28,5 +28,5 @@ interface IUniswapExchangeFactory { function getExchange(address tokenAddress) external view - returns (IUniswapExchange); + returns (address); } diff --git a/contracts/asset-proxy/contracts/test/TestUniswapBridge.sol b/contracts/asset-proxy/contracts/test/TestUniswapBridge.sol index 9f67714a1b..db3a14a254 100644 --- a/contracts/asset-proxy/contracts/test/TestUniswapBridge.sol +++ b/contracts/asset-proxy/contracts/test/TestUniswapBridge.sol @@ -407,9 +407,9 @@ contract TestUniswapBridge is function getExchange(address tokenAddress) external view - returns (IUniswapExchange) + returns (address) { - return IUniswapExchange(_testExchanges[tokenAddress]); + return address(_testExchanges[tokenAddress]); } // @dev Use `wethToken`. From fa768dc11279cf306026985844eff28580b8fe79 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:00:23 -0500 Subject: [PATCH 05/14] `@0x/contracts-erc20-bridge-sampler`: Finish off tests. --- .../contracts/src/DeploymentConstants.sol | 6 + .../contracts/src/ERC20BridgeSampler.sol | 38 +- .../contracts/src/IEth2Dai.sol | 2 +- .../contracts/src/IKyberNetwork.sol | 4 +- .../contracts/src/LibERC20Token.sol | 116 --- .../contracts/test/TestERC20BridgeSampler.sol | 189 +++-- contracts/erc20-bridge-sampler/package.json | 2 +- .../erc20-bridge-sampler/test/artifacts.ts | 2 - .../test/erc20-bridge-sampler.ts | 803 +++++++++++++++++- .../erc20-bridge-sampler/test/wrappers.ts | 1 - contracts/erc20-bridge-sampler/tsconfig.json | 1 - 11 files changed, 944 insertions(+), 220 deletions(-) delete mode 100644 contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol diff --git a/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol b/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol index cc99e2e62c..639329fe79 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/DeploymentConstants.sol @@ -27,11 +27,17 @@ import "./IKyberNetwork.sol"; contract DeploymentConstants { + /// @dev Address of the 0x Exchange contract. address constant public EXCHANGE_ADDRESS = 0x080bf510FCbF18b91105470639e9561022937712; + /// @dev Address of the Eth2Dai MatchingMarket contract. address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; + /// @dev Address of the UniswapExchangeFactory contract. address constant public UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; + /// @dev Address of the KyberNeworkProxy contract. address constant public KYBER_NETWORK_PROXY_ADDRESS = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; + /// @dev Address of the WETH contract. address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + /// @dev Kyber ETH pseudo-address. address constant public KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @dev An overridable way to retrieve the 0x Exchange contract. diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 897b32ff00..8eb7844b78 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -34,7 +34,7 @@ contract ERC20BridgeSampler is IERC20BridgeSampler, DeploymentConstants { - bytes4 constant internal ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)")); + bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); /// @dev Query native orders and sample sell quotes on multiple DEXes at once. /// @param orders Native orders to query. @@ -86,6 +86,7 @@ contract ERC20BridgeSampler is uint256[][] memory makerTokenAmountsBySource ) { + require(orders.length != 0, "EMPTY_ORDERS"); orderInfos = queryOrders(orders); makerTokenAmountsBySource = sampleBuys( sources, @@ -176,7 +177,7 @@ contract ERC20BridgeSampler is /// @param takerTokenAmounts Taker token sell amount for each sample. /// @return makerTokenAmounts Maker amounts bought at each taker token /// amount. - function sampleSellFromKyberNetwork( + function sampleSellsFromKyberNetwork( address takerToken, address makerToken, uint256[] memory takerTokenAmounts @@ -187,8 +188,8 @@ contract ERC20BridgeSampler is { address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken; address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken; - uint256 takerTokenDecimals = LibERC20Token.decimals(takerToken); - uint256 makerTokenDecimals = LibERC20Token.decimals(makerToken); + uint256 takerTokenDecimals = _getTokenDecimals(takerToken); + uint256 makerTokenDecimals = _getTokenDecimals(makerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { @@ -212,7 +213,7 @@ contract ERC20BridgeSampler is /// @param takerTokenAmounts Taker token sell amount for each sample. /// @return makerTokenAmounts Maker amounts bought at each taker token /// amount. - function sampleSellFromEth2Dai( + function sampleSellsFromEth2Dai( address takerToken, address makerToken, uint256[] memory takerTokenAmounts @@ -238,7 +239,7 @@ contract ERC20BridgeSampler is /// @param takerTokenAmounts Maker token sell amount for each sample. /// @return takerTokenAmounts Taker amounts sold at each maker token /// amount. - function sampleBuyFromEth2Dai( + function sampleBuysFromEth2Dai( address takerToken, address makerToken, uint256[] memory makerTokenAmounts @@ -264,7 +265,7 @@ contract ERC20BridgeSampler is /// @param takerTokenAmounts Taker token sell amount for each sample. /// @return makerTokenAmounts Maker amounts bought at each taker token /// amount. - function sampleSellFromUniswap( + function sampleSellsFromUniswap( address takerToken, address makerToken, uint256[] memory takerTokenAmounts @@ -305,7 +306,7 @@ contract ERC20BridgeSampler is /// @param makerTokenAmounts Maker token sell amount for each sample. /// @return takerTokenAmounts Taker amounts sold at each maker token /// amount. - function sampleBuyFromUniswap( + function sampleBuysFromUniswap( address takerToken, address makerToken, uint256[] memory makerTokenAmounts @@ -340,6 +341,17 @@ contract ERC20BridgeSampler is } } + /// @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) + internal + view + returns (uint8 decimals) + { + return LibERC20Token.decimals(tokenAddress); + } + /// @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). @@ -357,13 +369,13 @@ contract ERC20BridgeSampler is returns (uint256[] memory makerTokenAmounts) { if (source == address(_getEth2DaiContract())) { - return sampleSellFromEth2Dai(takerToken, makerToken, takerTokenAmounts); + return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts); } if (source == address(_getUniswapExchangeFactoryContract())) { - return sampleSellFromUniswap(takerToken, makerToken, takerTokenAmounts); + return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts); } if (source == address(_getKyberNetworkContract())) { - return sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); + return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); } revert("UNSUPPORTED_SOURCE"); } @@ -385,10 +397,10 @@ contract ERC20BridgeSampler is returns (uint256[] memory takerTokenAmounts) { if (source == address(_getEth2DaiContract())) { - return sampleBuyFromEth2Dai(takerToken, makerToken, makerTokenAmounts); + return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts); } if (source == address(_getUniswapExchangeFactoryContract())) { - return sampleBuyFromUniswap(takerToken, makerToken, makerTokenAmounts); + return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts); } revert("UNSUPPORTED_SOURCE"); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol index ea68aca77f..f4b38ca9c6 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IEth2Dai.sol @@ -19,7 +19,7 @@ pragma solidity ^0.5.9; -contract IEth2Dai { +interface IEth2Dai { function getBuyAmount( address buyToken, diff --git a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol index 4dc8ca134d..49c60dc408 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/IKyberNetwork.sol @@ -19,14 +19,14 @@ pragma solidity ^0.5.9; -contract IKyberNetwork { +interface IKyberNetwork { function getExpectedRate( address fromToken, address toToken, uint256 fromAmount ) - public + external view returns (uint256 expectedRate, uint256 slippageRate); } diff --git a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol b/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol deleted file mode 100644 index 97df57b0d9..0000000000 --- a/contracts/erc20-bridge-sampler/contracts/src/LibERC20Token.sol +++ /dev/null @@ -1,116 +0,0 @@ -/* - - 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; - - -library LibERC20Token { - - /// @dev Calls `IERC20Token(token).approve()`. - /// Reverts if `false` is returned or if the return - /// data length is nonzero and not 32 bytes. - /// @param token The address of the token contract. - /// @param spender The address that receives an allowance. - /// @param allowance The allowance to set. - function approve( - address token, - address spender, - uint256 allowance - ) - internal - { - bytes memory callData = abi.encodeWithSelector( - 0x095ea7b3, - spender, - allowance - ); - _callWithOptionalBooleanResult(token, callData); - } - - /// @dev Calls `IERC20Token(token).transfer()`. - /// Reverts if `false` is returned or if the return - /// data length is nonzero and not 32 bytes. - /// @param token The address of the token contract. - /// @param to The address that receives the tokens - /// @param amount Number of tokens to transfer. - function transfer( - address token, - address to, - uint256 amount - ) - internal - { - bytes memory callData = abi.encodeWithSelector( - 0xa9059cbb, - to, - amount - ); - _callWithOptionalBooleanResult(token, callData); - } - - /// @dev Calls `IERC20Token(token).transferFrom()`. - /// Reverts if `false` is returned or if the return - /// data length is nonzero and not 32 bytes. - /// @param token The address of the token contract. - /// @param from The owner of the tokens. - /// @param to The address that receives the tokens - /// @param amount Number of tokens to transfer. - function transferFrom( - address token, - address from, - address to, - uint256 amount - ) - internal - { - bytes memory callData = abi.encodeWithSelector( - 0x23b872dd, - from, - to, - amount - ); - _callWithOptionalBooleanResult(token, callData); - } - - /// @dev Executes a call on address `target` with calldata `callData` - /// and asserts that either nothing was returned or a single boolean - /// was returned equal to `true`. - /// @param target The call target. - /// @param callData The abi-encoded call data. - function _callWithOptionalBooleanResult( - address target, - bytes memory callData - ) - private - { - (bool didSucceed, bytes memory resultData) = target.call(callData); - if (didSucceed) { - if (resultData.length == 0) { - return; - } - if (resultData.length == 32) { - uint256 result; - assembly { result := mload(add(resultData, 0x20)) } - if (result == 1) { - return; - } - } - } - assembly { revert(add(resultData, 0x20), mload(resultData)) } - } -} diff --git a/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol index df9001cd69..e08b34cc35 100644 --- a/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/test/TestERC20BridgeSampler.sol @@ -28,8 +28,12 @@ import "../src/IKyberNetwork.sol"; library LibDeterministicQuotes { - uint256 private constant MAX_RATE = 10 ** 20; - uint256 private constant MIN_RATE = 10 ** 16; + address private constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint256 private constant RATE_DENOMINATOR = 1 ether; + uint256 private constant MIN_RATE = RATE_DENOMINATOR / 100; + uint256 private constant MAX_RATE = 100 * RATE_DENOMINATOR; + uint8 private constant MIN_DECIMALS = 4; + uint8 private constant MAX_DECIMALS = 20; function getDeterministicSellQuote( bytes32 salt, @@ -41,10 +45,10 @@ library LibDeterministicQuotes { pure returns (uint256 buyAmount) { - uint256 sellBase = 10 ** getDeterministicTokenDecimals(sellToken); - uint256 buyBase = 10 ** getDeterministicTokenDecimals(buyToken); - uint256 rate = _getDeterministicRate(salt, sellToken, buyToken); - return sellAmount * rate * buyBase / sellBase / 10 ** 18; + uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken); + uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken); + uint256 rate = getDeterministicRate(salt, sellToken, buyToken); + return sellAmount * rate * buyBase / sellBase / RATE_DENOMINATOR; } function getDeterministicBuyQuote( @@ -57,10 +61,10 @@ library LibDeterministicQuotes { pure returns (uint256 sellAmount) { - uint256 sellBase = 10 ** getDeterministicTokenDecimals(sellToken); - uint256 buyBase = 10 ** getDeterministicTokenDecimals(buyToken); - uint256 rate = _getDeterministicRate(salt, sellToken, buyToken); - return buyAmount * 10 ** 18 * sellBase / rate / buyBase; + uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken); + uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken); + uint256 rate = getDeterministicRate(salt, sellToken, buyToken); + return buyAmount * RATE_DENOMINATOR * sellBase / rate / buyBase; } function getDeterministicTokenDecimals(address token) @@ -68,32 +72,34 @@ library LibDeterministicQuotes { pure returns (uint8 decimals) { + if (token == WETH_ADDRESS) { + return 18; + } bytes32 seed = keccak256(abi.encodePacked(token)); - return uint8(uint256(seed) % 18) + 6; + return uint8(uint256(seed) % (MAX_DECIMALS - MIN_DECIMALS)) + MIN_DECIMALS; } - function _getDeterministicRate(bytes32 salt, address sellToken, address buyToken) - private + function getDeterministicRate(bytes32 salt, address sellToken, address buyToken) + internal pure returns (uint256 rate) { bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken)); - return MIN_RATE + uint256(seed) % MAX_RATE; + return uint256(seed) % (MAX_RATE - MIN_RATE) + MIN_RATE; } } contract TestERC20BridgeSamplerUniswapExchange is - IUniswapExchangeQuotes + IUniswapExchangeQuotes, + DeploymentConstants { bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab; address public tokenAddress; bytes32 public salt; - address private _wethAddress; constructor(address _tokenAddress) public { - _wethAddress = msg.sender; tokenAddress = _tokenAddress; salt = keccak256(abi.encodePacked(BASE_SALT, _tokenAddress)); } @@ -108,8 +114,8 @@ contract TestERC20BridgeSamplerUniswapExchange is { return LibDeterministicQuotes.getDeterministicSellQuote( salt, - _wethAddress, tokenAddress, + WETH_ADDRESS, ethSold ); } @@ -124,7 +130,7 @@ contract TestERC20BridgeSamplerUniswapExchange is { return LibDeterministicQuotes.getDeterministicBuyQuote( salt, - _wethAddress, + WETH_ADDRESS, tokenAddress, tokensBought ); @@ -141,7 +147,7 @@ contract TestERC20BridgeSamplerUniswapExchange is return LibDeterministicQuotes.getDeterministicSellQuote( salt, tokenAddress, - _wethAddress, + WETH_ADDRESS, tokensSold ); } @@ -156,63 +162,46 @@ contract TestERC20BridgeSamplerUniswapExchange is { return LibDeterministicQuotes.getDeterministicBuyQuote( salt, + WETH_ADDRESS, tokenAddress, - _wethAddress, ethBought ); } } -contract TestERC20BridgeSampler is - ERC20BridgeSampler +contract TestERC20BridgeSamplerKyberNetwork is + IKyberNetwork, + DeploymentConstants { - bytes32 constant private KYBER_SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; - bytes32 constant private ETH2DAI_SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7; - - mapping (address => IUniswapExchangeQuotes) private _uniswapExchangesByToken; - - // Creates Uniswap exchange contracts for tokens. - function createTokenExchanges(address[] calldata tokenAddresses) - external - { - for (uint256 i = 0; i < tokenAddresses.length; i++) { - address tokenAddress = tokenAddresses[i]; - _uniswapExchangesByToken[tokenAddress] = - new TestERC20BridgeSamplerUniswapExchange(tokenAddress); - } - } - - // `IERC20.decimals()` (for WETH). - function decimals() - external - view - returns (uint8 decimalPlaces) - { - return 18; - } + bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; + address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // Deterministic `IKyberNetwork.getExpectedRate()`. function getExpectedRate( address fromToken, address toToken, - uint256 fromAmount + uint256 ) external view - returns (uint256 expectedRate, uint256 slippageRate) + returns (uint256 expectedRate, uint256) { - uint256 quote = LibDeterministicQuotes.getDeterministicSellQuote( - KYBER_SALT, + fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken; + toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken; + expectedRate = LibDeterministicQuotes.getDeterministicRate( + SALT, fromToken, - toToken, - fromAmount + toToken ); - expectedRate = - 10 ** 18 * - (quote / LibDeterministicQuotes.getDeterministicTokenDecimals(toToken)) / - (fromAmount * LibDeterministicQuotes.getDeterministicTokenDecimals(fromToken)); } +} + + +contract TestERC20BridgeSamplerEth2Dai is + IEth2Dai +{ + bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7; // Deterministic `IEth2Dai.getBuyAmount()`. function getBuyAmount( @@ -225,7 +214,7 @@ contract TestERC20BridgeSampler is returns (uint256 buyAmount) { return LibDeterministicQuotes.getDeterministicSellQuote( - ETH2DAI_SALT, + SALT, payToken, buyToken, payAmount @@ -243,12 +232,30 @@ contract TestERC20BridgeSampler is returns (uint256 payAmount) { return LibDeterministicQuotes.getDeterministicBuyQuote( - ETH2DAI_SALT, + SALT, payToken, buyToken, buyAmount ); } +} + + +contract TestERC20BridgeSamplerUniswapExchangeFactory is + IUniswapExchangeFactory +{ + mapping (address => IUniswapExchangeQuotes) private _exchangesByToken; + + // Creates Uniswap exchange contracts for tokens. + function createTokenExchanges(address[] calldata tokenAddresses) + external + { + for (uint256 i = 0; i < tokenAddresses.length; i++) { + address tokenAddress = tokenAddresses[i]; + _exchangesByToken[tokenAddress] = + new TestERC20BridgeSamplerUniswapExchange(tokenAddress); + } + } // `IUniswapExchangeFactory.getExchange()`. function getExchange(address tokenAddress) @@ -256,13 +263,35 @@ contract TestERC20BridgeSampler is view returns (address) { - return address(_uniswapExchangesByToken[tokenAddress]); + return address(_exchangesByToken[tokenAddress]); + } +} + + +contract TestERC20BridgeSampler is + ERC20BridgeSampler +{ + TestERC20BridgeSamplerUniswapExchangeFactory public uniswap; + TestERC20BridgeSamplerEth2Dai public eth2Dai; + TestERC20BridgeSamplerKyberNetwork public kyber; + + constructor() public { + uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory(); + eth2Dai = new TestERC20BridgeSamplerEth2Dai(); + kyber = new TestERC20BridgeSamplerKyberNetwork(); + } + + // Creates Uniswap exchange contracts for tokens. + function createTokenExchanges(address[] calldata tokenAddresses) + external + { + uniswap.createTokenExchanges(tokenAddresses); } // `IExchange.getOrderInfo()`, overridden to return deterministic order infos. function getOrderInfo(LibOrder.Order memory order) public - view + pure returns (LibOrder.OrderInfo memory orderInfo) { // The order hash is just the hash of the salt. @@ -273,7 +302,16 @@ contract TestERC20BridgeSampler is orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount; } - // Overriden to point to this contract. + // Overriden to return deterministic decimals. + function _getTokenDecimals(address tokenAddress) + internal + view + returns (uint8 decimals) + { + return LibDeterministicQuotes.getDeterministicTokenDecimals(tokenAddress); + } + + // Overriden to point to a this contract. function _getExchangeContract() internal view @@ -282,39 +320,30 @@ contract TestERC20BridgeSampler is return IExchange(address(this)); } - // Overriden to point to this contract. + // Overriden to point to a custom contract. function _getEth2DaiContract() internal view - returns (IEth2Dai eth2dai) + returns (IEth2Dai eth2dai_) { - return IEth2Dai(address(this)); + return eth2Dai; } - // Overriden to point to this contract. + // Overriden to point to a custom contract. function _getUniswapExchangeFactoryContract() internal view - returns (IUniswapExchangeFactory uniswap) + returns (IUniswapExchangeFactory uniswap_) { - return IUniswapExchangeFactory(address(this)); + return uniswap; } - // Overriden to point to this contract. + // Overriden to point to a custom contract. function _getKyberNetworkContract() internal view - returns (IKyberNetwork kyber) + returns (IKyberNetwork kyber_) { - return IKyberNetwork(address(this)); - } - - // Overriden to point to this contract. - function _getWETHAddress() - internal - view - returns (address weth) - { - return address(this); + return kyber; } } diff --git a/contracts/erc20-bridge-sampler/package.json b/contracts/erc20-bridge-sampler/package.json index b96ed8b7b8..fcd42c381a 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/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|LibERC20Token|TestERC20BridgeSampler).json" + "abis": "./test/generated-artifacts/@(DeploymentConstants|ERC20BridgeSampler|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|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 388c59b542..a6910076ce 100644 --- a/contracts/erc20-bridge-sampler/test/artifacts.ts +++ b/contracts/erc20-bridge-sampler/test/artifacts.ts @@ -11,7 +11,6 @@ import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSa import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; -import * as LibERC20Token from '../test/generated-artifacts/LibERC20Token.json'; import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; export const artifacts = { DeploymentConstants: DeploymentConstants as ContractArtifact, @@ -20,6 +19,5 @@ export const artifacts = { IEth2Dai: IEth2Dai as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, - LibERC20Token: LibERC20Token as ContractArtifact, TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact, }; diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index 33740967a7..337c017142 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -1,11 +1,45 @@ -import { blockchainTests, expect, getRandomInteger } from '@0x/contracts-test-utils'; +import { + blockchainTests, + constants, + expect, + getRandomInteger, + getRandomPortion, + hexConcat, + hexHash, + hexLeftPad, + hexRandom, + randomAddress, + toHex, +} from '@0x/contracts-test-utils'; +import { Order, OrderInfo } from '@0x/types'; import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { TestERC20BridgeSamplerContract } from './wrappers'; blockchainTests('erc20-bridge-sampler', env => { let testContract: TestERC20BridgeSamplerContract; + let allSources: { [name: string]: string }; + const RATE_DENOMINATOR = constants.ONE_ETHER; + const MIN_RATE = new BigNumber('0.01'); + const MAX_RATE = new BigNumber('100'); + const MIN_DECIMALS = 4; + const MAX_DECIMALS = 20; + const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + const KYBER_SALT = '0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7'; + const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7'; + const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; + const ERC20_PROXY_ID = '0xf47261b0'; + const INVALID_ASSET_PROXY_ASSET_DATA = hexConcat('0xf47261b1', hexLeftPad(randomAddress())); + const INVALID_ASSET_DATA = hexRandom(37); + const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap']; + const BUY_SOURCES = ['Eth2Dai', 'Uniswap']; + const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS'; + const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY'; + const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA'; + const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE'; + const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE'; before(async () => { testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( @@ -14,11 +48,774 @@ blockchainTests('erc20-bridge-sampler', env => { env.txDefaults, {}, ); + allSources = _.zipObject( + ['Uniswap', 'Eth2Dai', 'Kyber'], + [ + await testContract.uniswap().callAsync(), + await testContract.eth2Dai().callAsync(), + await testContract.kyber().callAsync(), + ], + ); }); - describe('queryOrders()', () => { - it('returns the results of `getOrderInfo()` for each order', async () => { + function getPackedHash(...args: string[]): string { + return hexHash(hexConcat(...args.map(a => toHex(a)))); + } + function getUniswapExchangeSalt(tokenAddress: string): string { + return getPackedHash(UNISWAP_BASE_SALT, tokenAddress); + } + + function getDeterministicRate(salt: string, sellToken: string, buyToken: string): BigNumber { + const hash = getPackedHash(salt, sellToken, buyToken); + const _minRate = RATE_DENOMINATOR.times(MIN_RATE); + const _maxRate = RATE_DENOMINATOR.times(MAX_RATE); + return new BigNumber(hash) + .mod(_maxRate.minus(_minRate)) + .plus(_minRate) + .div(RATE_DENOMINATOR); + } + + function getDeterministicTokenDecimals(token: string): number { + if (token === WETH_ADDRESS) { + return 18; + } + return ( + (new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() as number) + MIN_DECIMALS // linter is confused + ); + } + + function getDeterministicSellQuote( + salt: string, + sellToken: string, + buyToken: string, + sellAmount: BigNumber, + ): BigNumber { + const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); + const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); + const rate = getDeterministicRate(salt, sellToken, buyToken); + return sellAmount + .times(rate) + .times(buyBase) + .dividedToIntegerBy(sellBase); + } + + function getDeterministicBuyQuote( + salt: string, + sellToken: string, + buyToken: string, + buyAmount: BigNumber, + ): BigNumber { + const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); + const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); + const rate = getDeterministicRate(salt, sellToken, buyToken); + return buyAmount + .times(sellBase) + .dividedToIntegerBy(rate) + .dividedToIntegerBy(buyBase); + } + + function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber { + if (buyToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount); + } + if (sellToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, sellAmount); + } + const ethBought = getDeterministicSellQuote( + getUniswapExchangeSalt(sellToken), + sellToken, + WETH_ADDRESS, + sellAmount, + ); + return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, ethBought); + } + + function getDeterministicUniswapBuyQuote(sellToken: string, buyToken: string, buyAmount: BigNumber): BigNumber { + if (buyToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount); + } + if (sellToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + return getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); + } + const ethSold = getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); + return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, ethSold); + } + + function getDeterministicSellQuotes( + sellToken: string, + buyToken: string, + sources: string[], + sampleAmounts: BigNumber[], + ): BigNumber[][] { + const quotes: BigNumber[][] = []; + for (const source of sources) { + const sampleOutputs = []; + for (const amount of sampleAmounts) { + if (source === 'Kyber' || source === 'Eth2Dai') { + sampleOutputs.push( + getDeterministicSellQuote( + source === 'Kyber' ? KYBER_SALT : ETH2DAI_SALT, + sellToken, + buyToken, + amount, + ), + ); + } else if (source === 'Uniswap') { + sampleOutputs.push(getDeterministicUniswapSellQuote(sellToken, buyToken, amount)); + } + } + quotes.push(sampleOutputs); + } + return quotes; + } + + function getDeterministicBuyQuotes( + sellToken: string, + buyToken: string, + sources: string[], + sampleAmounts: BigNumber[], + ): BigNumber[][] { + const quotes: BigNumber[][] = []; + for (const source of sources) { + const sampleOutputs = []; + for (const amount of sampleAmounts) { + if (source === 'Eth2Dai') { + sampleOutputs.push(getDeterministicBuyQuote(ETH2DAI_SALT, sellToken, buyToken, amount)); + } else if (source === 'Uniswap') { + sampleOutputs.push(getDeterministicUniswapBuyQuote(sellToken, buyToken, amount)); + } + } + quotes.push(sampleOutputs); + } + return quotes; + } + + function getDeterministicOrderInfo(order: Order): OrderInfo { + const hash = getPackedHash(toHex(order.salt, 32)); + return { + orderHash: hash, + orderStatus: new BigNumber(hash).mod(255).toNumber(), + orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount), + }; + } + + function getERC20AssetData(tokenAddress: string): string { + return hexConcat(ERC20_PROXY_ID, hexLeftPad(tokenAddress)); + } + + function getSampleAmounts(tokenAddress: string, count?: number): BigNumber[] { + const tokenDecimals = getDeterministicTokenDecimals(tokenAddress); + const _upperLimit = getRandomPortion(getRandomInteger(1, 1000).times(10 ** tokenDecimals)); + const _count = count || _.random(1, 16); + const d = _upperLimit.div(_count); + return _.times(_count, i => d.times((i + 1) / _count).integerValue()); + } + + function createOrder(makerToken: string, takerToken: string): Order { + return { + chainId: 1337, + exchangeAddress: randomAddress(), + makerAddress: randomAddress(), + takerAddress: randomAddress(), + senderAddress: randomAddress(), + feeRecipientAddress: randomAddress(), + makerAssetAmount: getRandomInteger(1, 1e18), + takerAssetAmount: getRandomInteger(1, 1e18), + makerFee: getRandomInteger(1, 1e18), + takerFee: getRandomInteger(1, 1e18), + makerAssetData: getERC20AssetData(makerToken), + takerAssetData: getERC20AssetData(takerToken), + makerFeeAssetData: getERC20AssetData(randomAddress()), + takerFeeAssetData: getERC20AssetData(randomAddress()), + salt: new BigNumber(hexRandom()), + expirationTimeSeconds: getRandomInteger(0, 2 ** 32), + }; + } + + function createOrders(makerToken: string, takerToken: string, count?: number): Order[] { + return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); + } + + describe('queryOrders()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + it('returns the results of `getOrderInfo()` for each order', async () => { + const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); + const expected = orders.map(getDeterministicOrderInfo); + const actual = await testContract.queryOrders(orders).callAsync(); + expect(actual).to.deep.eq(expected); + }); + + it('returns empty for no orders', async () => { + const actual = await testContract.queryOrders([]).callAsync(); + expect(actual).to.deep.eq([]); + }); + }); + + describe('queryOrdersAndSampleSells()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('returns the results of `getOrderInfo()` for each order', async () => { + const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN); + const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); + const expectedOrderInfos = orders.map(getDeterministicOrderInfo); + const [orderInfos] = await testContract + .queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts) + .callAsync(); + expect(orderInfos).to.deep.eq(expectedOrderInfos); + }); + + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN), + SELL_SOURCES.map(n => allSources[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 => allSources[n]), getSampleAmounts(TAKER_TOKEN)) + .callAsync(); + return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); + }); + + it('throws with an unsupported source', async () => { + const tx = testContract + .queryOrdersAndSampleSells( + createOrders(MAKER_TOKEN, TAKER_TOKEN), + [...SELL_SOURCES.map(n => allSources[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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, + })), + [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, + })), + [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + makerAssetData: INVALID_ASSET_DATA, + })), + [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + takerAssetData: INVALID_ASSET_DATA, + })), + [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + getSampleAmounts(TAKER_TOKEN), + ) + .callAsync(); + return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); + }); + }); + + describe('queryOrdersAndSampleBuys()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('returns the results of `getOrderInfo()` for each order', async () => { + const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN); + const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); + const expectedOrderInfos = orders.map(getDeterministicOrderInfo); + const [orderInfos] = await testContract + .queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts) + .callAsync(); + expect(orderInfos).to.deep.eq(expectedOrderInfos); + }); + + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN), + BUY_SOURCES.map(n => allSources[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 => allSources[n]), getSampleAmounts(MAKER_TOKEN)) + .callAsync(); + return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); + }); + + it('throws with an unsupported source', async () => { + const tx = testContract + .queryOrdersAndSampleBuys( + createOrders(MAKER_TOKEN, TAKER_TOKEN), + [...BUY_SOURCES.map(n => allSources[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( + createOrders(MAKER_TOKEN, TAKER_TOKEN), + sources.map(n => allSources[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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, + })), + [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, + })), + [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + makerAssetData: INVALID_ASSET_DATA, + })), + [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + 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( + createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ + ...o, + takerAssetData: INVALID_ASSET_DATA, + })), + [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + getSampleAmounts(MAKER_TOKEN), + ) + .callAsync(); + return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); + }); + }); + + describe('sampleSells()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + 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 => allSources[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 => allSources[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 => allSources[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 => allSources[n]), randomAddress()], + TAKER_TOKEN, + MAKER_TOKEN, + getSampleAmounts(TAKER_TOKEN), + ) + .callAsync(); + return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); + }); + }); + + describe('sampleBuys()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + 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 => allSources[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 => allSources[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 => allSources[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 => allSources[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 => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(MAKER_TOKEN)) + .callAsync(); + return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); + }); + }); + + describe('sampleSellsFromKyberNetwork()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('can return no quotes', async () => { + const quotes = await testContract.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + expect(quotes).to.deep.eq([]); + }); + + it('can return many quotes', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> ETH', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote ETH -> token', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + }); + + describe('sampleSellsFromEth2Dai()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('can return no quotes', async () => { + const quotes = await testContract.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + expect(quotes).to.deep.eq([]); + }); + + it('can return many quotes', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> ETH', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote ETH -> token', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + }); + + describe('sampleBuysFromEth2Dai()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('can return no quotes', async () => { + const quotes = await testContract.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + expect(quotes).to.deep.eq([]); + }); + + it('can return many quotes', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> ETH', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote ETH -> token', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + }); + + describe('sampleSellsFromUniswap()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('can return no quotes', async () => { + const quotes = await testContract.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + expect(quotes).to.deep.eq([]); + }); + + it('can return many quotes', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> ETH', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote ETH -> token', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('throws if no exchange exists for the maker token', async () => { + const nonExistantToken = randomAddress(); + const tx = testContract + .sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) + .callAsync(); + expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + }); + + it('throws if no exchange exists for the taker token', async () => { + const nonExistantToken = randomAddress(); + const tx = testContract + .sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) + .callAsync(); + expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + }); + }); + + describe('sampleBuysFromUniswap()', () => { + const MAKER_TOKEN = randomAddress(); + const TAKER_TOKEN = randomAddress(); + + before(async () => { + await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); + }); + + it('can return no quotes', async () => { + const quotes = await testContract.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + expect(quotes).to.deep.eq([]); + }); + + it('can return many quotes', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> ETH', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote ETH -> token', async () => { + const sampleAmounts = getSampleAmounts(MAKER_TOKEN); + const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); + const quotes = await testContract + .sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('throws if no exchange exists for the maker token', async () => { + const nonExistantToken = randomAddress(); + const tx = testContract + .sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) + .callAsync(); + expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + }); + + it('throws if no exchange exists for the taker token', async () => { + const nonExistantToken = randomAddress(); + const tx = testContract + .sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) + .callAsync(); + expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); }); }); diff --git a/contracts/erc20-bridge-sampler/test/wrappers.ts b/contracts/erc20-bridge-sampler/test/wrappers.ts index c4980e30fd..f1b8501d01 100644 --- a/contracts/erc20-bridge-sampler/test/wrappers.ts +++ b/contracts/erc20-bridge-sampler/test/wrappers.ts @@ -9,5 +9,4 @@ 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_uniswap_exchange_quotes'; -export * from '../test/generated-wrappers/lib_erc20_token'; 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 d5a7f69ea0..2c25499f48 100644 --- a/contracts/erc20-bridge-sampler/tsconfig.json +++ b/contracts/erc20-bridge-sampler/tsconfig.json @@ -11,7 +11,6 @@ "test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IKyberNetwork.json", "test/generated-artifacts/IUniswapExchangeQuotes.json", - "test/generated-artifacts/LibERC20Token.json", "test/generated-artifacts/TestERC20BridgeSampler.json" ], "exclude": ["./deploy/solc/solc_bin"] From 5d1a7613dd23cd59213273e2baed7337769ce922 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:14:10 -0500 Subject: [PATCH 06/14] Add `@0x/contracts-erc20-bridge-sampler` to CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc729db6f0..5526308f1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,7 +77,7 @@ jobs: - restore_cache: keys: - repo-{{ .Environment.CIRCLE_SHA1 }} - - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator + - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-tests @0x/contracts-staking @0x/contracts-coordinator @0x/contracts-erc20-bridge-sampler # TODO(dorothy-zbornak): Re-enable after updating this package for # 3.0. At that time, also remove exclusion from monorepo # package.json's test script. From 5567c40bae61379b28ded24d1ff5bb30da85e60b Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:17:53 -0500 Subject: [PATCH 07/14] Update changelogs --- contracts/erc20-bridge-sampler/CHANGELOG.json | 2 +- contracts/erc20/CHANGELOG.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/erc20-bridge-sampler/CHANGELOG.json b/contracts/erc20-bridge-sampler/CHANGELOG.json index c658665484..8e91fd1d2c 100644 --- a/contracts/erc20-bridge-sampler/CHANGELOG.json +++ b/contracts/erc20-bridge-sampler/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": "Created package.", - "pr": "TODO" + "pr": 2344 } ] } diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index c9aa042288..5d5d1e870f 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -17,7 +17,7 @@ }, { "note": "Add `decimals()` to `LibERC20Token`.", - "pr": "TODO" + "pr": 2344 } ], "timestamp": 1574030254 From f00524e5188894cfd671794705174e07fb012fb1 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:21:41 -0500 Subject: [PATCH 08/14] `@0x/contracts-erc20-bridge-sampler`: Update README --- contracts/erc20-bridge-sampler/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/erc20-bridge-sampler/README.md b/contracts/erc20-bridge-sampler/README.md index 7ab3cb2306..75017ed771 100644 --- a/contracts/erc20-bridge-sampler/README.md +++ b/contracts/erc20-bridge-sampler/README.md @@ -1,3 +1,5 @@ ## ERC20BridgeSampler -These are contracts used in DEX aggregation. +These contracts are used in DEX aggregation. + +This is an MVP implementation, which agnostically samples DEXes for off-chain sorting and order generation. It is entirely read-only and never not touches any funds. From 57731be689de0b60794a80a83eaf3e5704b62162 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:24:52 -0500 Subject: [PATCH 09/14] `@0x/contracts-erc20-bridge-sampler`: Remove gitkeep files --- contracts/erc20-bridge-sampler/src/.gitkeep | 0 contracts/erc20-bridge-sampler/test/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 contracts/erc20-bridge-sampler/src/.gitkeep delete mode 100644 contracts/erc20-bridge-sampler/test/.gitkeep diff --git a/contracts/erc20-bridge-sampler/src/.gitkeep b/contracts/erc20-bridge-sampler/src/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/contracts/erc20-bridge-sampler/test/.gitkeep b/contracts/erc20-bridge-sampler/test/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From c7d801b6c20d4fb9d48e0c240327813a4426dd17 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:27:53 -0500 Subject: [PATCH 10/14] `@0x/contracts-erc20-bridge-sampler`: Update `DEPLOYS.json` --- contracts/erc20-bridge-sampler/DEPLOYS.json | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/contracts/erc20-bridge-sampler/DEPLOYS.json b/contracts/erc20-bridge-sampler/DEPLOYS.json index 2eea680696..fe51488c70 100644 --- a/contracts/erc20-bridge-sampler/DEPLOYS.json +++ b/contracts/erc20-bridge-sampler/DEPLOYS.json @@ -1,17 +1 @@ -[ - { - "name": "Exchange", - "version": "2.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0xBDB58Fdf0a1587a0a258330a184Bb2C6cbA0aabe", - "3": "0x0000000000000000000000000000000000000000", - "4": "0x0000000000000000000000000000000000000000", - "42": "0x0000000000000000000000000000000000000000" - } - } - ] - } -] +[] From 39571dda0bcd4b79f265ba3ae7cb450eb7bf892d Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:35:26 -0500 Subject: [PATCH 11/14] Add `erc20-bridge-sampler` to prettierignore --- .prettierignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.prettierignore b/.prettierignore index 1435fa7dd3..f1ca1c7f39 100644 --- a/.prettierignore +++ b/.prettierignore @@ -36,6 +36,10 @@ lib /contracts/erc20/test/generated-wrappers /contracts/erc20/generated-artifacts /contracts/erc20/test/generated-artifacts +/contracts/erc20-bridge-sampler/generated-wrappers +/contracts/erc20-bridge-sampler/test/generated-wrappers +/contracts/erc20-bridge-sampler/generated-artifacts +/contracts/erc20-bridge-sampler/test/generated-artifacts /contracts/erc721/generated-wrappers /contracts/erc721/test/generated-wrappers /contracts/erc721/generated-artifacts From dea30b37efadc6a961a4b6ce140d19d851b09f13 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Sat, 16 Nov 2019 14:51:41 -0500 Subject: [PATCH 12/14] `@0x/contracts-erc20-bridge-sampler`: Update README and add `index.ts`. --- contracts/erc20-bridge-sampler/README.md | 64 ++++++++++++++++++++- contracts/erc20-bridge-sampler/src/index.ts | 2 + 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 contracts/erc20-bridge-sampler/src/index.ts diff --git a/contracts/erc20-bridge-sampler/README.md b/contracts/erc20-bridge-sampler/README.md index 75017ed771..3a04e3b8bd 100644 --- a/contracts/erc20-bridge-sampler/README.md +++ b/contracts/erc20-bridge-sampler/README.md @@ -1,5 +1,67 @@ ## ERC20BridgeSampler -These contracts are used in DEX aggregation. +This package contains contracts used in DEX aggregation. This is an MVP implementation, which agnostically samples DEXes for off-chain sorting and order generation. It is entirely read-only and never not touches any funds. + +## Installation + +**Install** + +```bash +npm install @0x/contracts-erc20-bridge-sampler --save +``` + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-erc20-bridge-sampler yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-erc20-bridge-sampler yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/contracts/erc20-bridge-sampler/src/index.ts b/contracts/erc20-bridge-sampler/src/index.ts new file mode 100644 index 0000000000..91dd7e0e3e --- /dev/null +++ b/contracts/erc20-bridge-sampler/src/index.ts @@ -0,0 +1,2 @@ +export * from './wrappers'; +export * from './artifacts'; From a8e93a594d2ad9e0427c4092531650b2b9a5a3be Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 19 Nov 2019 20:35:32 -0500 Subject: [PATCH 13/14] `@0x/contracts-erc20-bridge-sampler`: Throw sampling two of the same tokens. `@0x/contracts-erc20-bridge-sampler`: Address review comments. --- .../contracts/src/ERC20BridgeSampler.sol | 12 ++++ .../test/erc20-bridge-sampler.ts | 62 ++++++++++++++----- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol index 8eb7844b78..235ffa79d5 100644 --- a/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol +++ b/contracts/erc20-bridge-sampler/contracts/src/ERC20BridgeSampler.sol @@ -186,6 +186,7 @@ contract ERC20BridgeSampler is view returns (uint256[] memory makerTokenAmounts) { + _assertValidPair(makerToken, takerToken); address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken; address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken; uint256 takerTokenDecimals = _getTokenDecimals(takerToken); @@ -222,6 +223,7 @@ contract ERC20BridgeSampler is view returns (uint256[] memory makerTokenAmounts) { + _assertValidPair(makerToken, takerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { @@ -248,6 +250,7 @@ contract ERC20BridgeSampler is view returns (uint256[] memory takerTokenAmounts) { + _assertValidPair(makerToken, takerToken); uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); for (uint256 i = 0; i < numSamples; i++) { @@ -274,6 +277,7 @@ contract ERC20BridgeSampler is view returns (uint256[] memory makerTokenAmounts) { + _assertValidPair(makerToken, takerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? @@ -315,6 +319,7 @@ contract ERC20BridgeSampler is view returns (uint256[] memory takerTokenAmounts) { + _assertValidPair(makerToken, takerToken); uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? @@ -436,4 +441,11 @@ contract ERC20BridgeSampler is } require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY"); } + + function _assertValidPair(address makerToken, address takerToken) + private + pure + { + require(makerToken != takerToken, "INVALID_TOKEN_PAIR"); + } } diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index 337c017142..83b747028b 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -40,6 +40,7 @@ blockchainTests('erc20-bridge-sampler', env => { const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA'; const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE'; const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE'; + const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR'; before(async () => { testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( @@ -115,11 +116,15 @@ blockchainTests('erc20-bridge-sampler', env => { .dividedToIntegerBy(buyBase); } + function areAddressesEqual(a: string, b: string): boolean { + return a.toLowerCase() === b.toLowerCase(); + } + function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber { - if (buyToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + if (areAddressesEqual(buyToken, WETH_ADDRESS)) { return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount); } - if (sellToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + if (areAddressesEqual(sellToken, WETH_ADDRESS)) { return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, sellAmount); } const ethBought = getDeterministicSellQuote( @@ -132,10 +137,10 @@ blockchainTests('erc20-bridge-sampler', env => { } function getDeterministicUniswapBuyQuote(sellToken: string, buyToken: string, buyAmount: BigNumber): BigNumber { - if (buyToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + if (areAddressesEqual(buyToken, WETH_ADDRESS)) { return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount); } - if (sellToken.toLowerCase() === WETH_ADDRESS.toLowerCase()) { + if (areAddressesEqual(sellToken, WETH_ADDRESS)) { return getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); } const ethSold = getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); @@ -310,7 +315,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), - [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); @@ -324,7 +329,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), - [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); @@ -338,7 +343,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, makerAssetData: INVALID_ASSET_DATA, })), - [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); @@ -352,7 +357,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, takerAssetData: INVALID_ASSET_DATA, })), - [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], + SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); @@ -428,7 +433,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), - [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); @@ -442,7 +447,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), - [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); @@ -456,7 +461,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, makerAssetData: INVALID_ASSET_DATA, })), - [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); @@ -470,7 +475,7 @@ blockchainTests('erc20-bridge-sampler', env => { ...o, takerAssetData: INVALID_ASSET_DATA, })), - [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], + BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); @@ -590,6 +595,11 @@ blockchainTests('erc20-bridge-sampler', env => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); + it('throws if tokens are the same', async () => { + const tx = testContract.sampleSellsFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); + }); + it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); @@ -631,6 +641,11 @@ blockchainTests('erc20-bridge-sampler', env => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); + it('throws if tokens are the same', async () => { + const tx = testContract.sampleSellsFromEth2Dai(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); + }); + it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); @@ -672,6 +687,11 @@ blockchainTests('erc20-bridge-sampler', env => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); + it('throws if tokens are the same', async () => { + const tx = testContract.sampleBuysFromEth2Dai(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); + }); + it('can return no quotes', async () => { const quotes = await testContract.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); @@ -713,6 +733,11 @@ blockchainTests('erc20-bridge-sampler', env => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); + it('throws if tokens are the same', async () => { + const tx = testContract.sampleSellsFromUniswap(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); + }); + it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); @@ -750,7 +775,7 @@ blockchainTests('erc20-bridge-sampler', env => { const tx = testContract .sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) .callAsync(); - expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); it('throws if no exchange exists for the taker token', async () => { @@ -758,7 +783,7 @@ blockchainTests('erc20-bridge-sampler', env => { const tx = testContract .sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) .callAsync(); - expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); }); @@ -770,6 +795,11 @@ blockchainTests('erc20-bridge-sampler', env => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); + it('throws if tokens are the same', async () => { + const tx = testContract.sampleBuysFromUniswap(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); + }); + it('can return no quotes', async () => { const quotes = await testContract.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); @@ -807,7 +837,7 @@ blockchainTests('erc20-bridge-sampler', env => { const tx = testContract .sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) .callAsync(); - expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); it('throws if no exchange exists for the taker token', async () => { @@ -815,7 +845,7 @@ blockchainTests('erc20-bridge-sampler', env => { const tx = testContract .sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) .callAsync(); - expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); + return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); }); }); From 1462ab08de5e3ac4120bf9a3e5d7ad0d71191753 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Mon, 25 Nov 2019 17:55:12 -0500 Subject: [PATCH 14/14] `@0x/contracts-erc20-bridge-sampler`: Clean up linter workaround in tests. --- .../erc20-bridge-sampler/test/erc20-bridge-sampler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index 83b747028b..2f47d7dca0 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -81,9 +81,10 @@ blockchainTests('erc20-bridge-sampler', env => { if (token === WETH_ADDRESS) { return 18; } - return ( - (new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() as number) + MIN_DECIMALS // linter is confused - ); + // HACK(dorothy-zbornak): Linter will complain about the addition not being + // between two numbers, even though they are. + // tslint:disable-next-line restrict-plus-operands + return new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() + MIN_DECIMALS; } function getDeterministicSellQuote(