From 0af346aad8b691dad805c5b27e35e23046d10342 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Thu, 17 Oct 2019 20:42:53 -0400 Subject: [PATCH] `@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); + }); + }); });