Merge pull request #2344 from 0xProject/feat/erc20-bridge-aggregator

ERC20BridgeSampler
This commit is contained in:
Lawrence Forman
2019-11-25 20:33:19 -05:00
committed by GitHub
36 changed files with 2483 additions and 41 deletions

View File

@@ -77,7 +77,7 @@ jobs:
- restore_cache: - restore_cache:
keys: keys:
- repo-{{ .Environment.CIRCLE_SHA1 }} - 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 # TODO(dorothy-zbornak): Re-enable after updating this package for
# 3.0. At that time, also remove exclusion from monorepo # 3.0. At that time, also remove exclusion from monorepo
# package.json's test script. # package.json's test script.

5
.gitignore vendored
View File

@@ -79,6 +79,8 @@ TODO.md
.vscode .vscode
# generated contract artifacts/ # generated contract artifacts/
contracts/erc20-bridge-sampler/generated-artifacts/
contracts/erc20-bridge-sampler/test/generated-artifacts/
contracts/integrations/generated-artifacts/ contracts/integrations/generated-artifacts/
contracts/integrations/test/generated-artifacts/ contracts/integrations/test/generated-artifacts/
contracts/staking/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/ python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/
# generated truffle contract artifacts/ # generated truffle contract artifacts/
contracts/erc20-bridge-sampler/build/
contracts/staking/build/ contracts/staking/build/
contracts/coordinator/build/ contracts/coordinator/build/
contracts/exchange/build/ contracts/exchange/build/
@@ -127,6 +130,8 @@ contracts/dev-utils/build/
# generated contract wrappers # generated contract wrappers
packages/python-contract-wrappers/generated/ packages/python-contract-wrappers/generated/
contracts/erc20-bridge-sampler/generated-wrappers/
contracts/erc20-bridge-sampler/test/generated-wrappers/
contracts/integrations/generated-wrappers/ contracts/integrations/generated-wrappers/
contracts/integrations/test/generated-wrappers/ contracts/integrations/test/generated-wrappers/
contracts/staking/generated-wrappers/ contracts/staking/generated-wrappers/

View File

@@ -36,6 +36,10 @@ lib
/contracts/erc20/test/generated-wrappers /contracts/erc20/test/generated-wrappers
/contracts/erc20/generated-artifacts /contracts/erc20/generated-artifacts
/contracts/erc20/test/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/generated-wrappers
/contracts/erc721/test/generated-wrappers /contracts/erc721/test/generated-wrappers
/contracts/erc721/generated-artifacts /contracts/erc721/generated-artifacts

View File

@@ -210,7 +210,9 @@ contract UniswapBridge is
if (fromTokenAddress == address(getWethContract())) { if (fromTokenAddress == address(getWethContract())) {
exchangeTokenAddress = toTokenAddress; exchangeTokenAddress = toTokenAddress;
} }
exchange = getUniswapExchangeFactoryContract().getExchange(exchangeTokenAddress); exchange = IUniswapExchange(
getUniswapExchangeFactoryContract().getExchange(exchangeTokenAddress)
);
require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN"); require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
return exchange; return exchange;
} }

View File

@@ -67,11 +67,4 @@ interface IUniswapExchange {
) )
external external
returns (uint256 tokensBought); 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);
} }

View File

@@ -28,5 +28,5 @@ interface IUniswapExchangeFactory {
function getExchange(address tokenAddress) function getExchange(address tokenAddress)
external external
view view
returns (IUniswapExchange); returns (address);
} }

View File

@@ -407,9 +407,9 @@ contract TestUniswapBridge is
function getExchange(address tokenAddress) function getExchange(address tokenAddress)
external external
view view
returns (IUniswapExchange) returns (address)
{ {
return IUniswapExchange(_testExchanges[tokenAddress]); return address(_testExchanges[tokenAddress]);
} }
// @dev Use `wethToken`. // @dev Use `wethToken`.

View File

@@ -0,0 +1,10 @@
# Blacklist all files
.*
*
# Whitelist lib
!lib/**/*
# Whitelist Solidity contracts
!contracts/src/**/*
# Blacklist tests in lib
/lib/test/*
# Package specific ignore

View File

@@ -0,0 +1,11 @@
[
{
"version": "1.0.0-beta.1",
"changes": [
{
"note": "Created package.",
"pr": 2344
}
]
}
]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,67 @@
## ERC20BridgeSampler
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
```

View File

@@ -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"
]
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
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 {
/// @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.
/// @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;
}
}

View File

@@ -0,0 +1,451 @@
/*
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 "./IEth2Dai.sol";
import "./IKyberNetwork.sol";
import "./IUniswapExchangeQuotes.sol";
import "./DeploymentConstants.sol";
contract ERC20BridgeSampler is
IERC20BridgeSampler,
DeploymentConstants
{
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.
/// @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(
LibOrder.Order[] memory orders,
address[] memory sources,
uint256[] memory takerTokenAmounts
)
public
view
returns (
LibOrder.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
);
}
/// @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(
LibOrder.Order[] memory orders,
address[] memory sources,
uint256[] memory makerTokenAmounts
)
public
view
returns (
LibOrder.OrderInfo[] memory orderInfos,
uint256[][] memory makerTokenAmountsBySource
)
{
require(orders.length != 0, "EMPTY_ORDERS");
orderInfos = queryOrders(orders);
makerTokenAmountsBySource = sampleBuys(
sources,
_assetDataToTokenAddress(orders[0].takerAssetData),
_assetDataToTokenAddress(orders[0].makerAssetData),
makerTokenAmounts
);
}
/// @dev Queries the status of several native orders.
/// @param orders Native orders to query.
/// @return orderInfos Order info for each respective order.
function queryOrders(LibOrder.Order[] memory orders)
public
view
returns (LibOrder.OrderInfo[] memory orderInfos)
{
uint256 numOrders = orders.length;
orderInfos = new LibOrder.OrderInfo[](numOrders);
for (uint256 i = 0; i < numOrders; i++) {
orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]);
}
}
/// @dev Sample sell quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function sampleSells(
address[] memory sources,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[][] memory makerTokenAmountsBySource)
{
uint256 numSources = sources.length;
makerTokenAmountsBySource = new uint256[][](numSources);
for (uint256 i = 0; i < numSources; i++) {
makerTokenAmountsBySource[i] = _sampleSellSource(
sources[i],
takerToken,
makerToken,
takerTokenAmounts
);
}
}
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function sampleBuys(
address[] memory sources,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[][] memory takerTokenAmountsBySource)
{
uint256 numSources = sources.length;
takerTokenAmountsBySource = new uint256[][](numSources);
for (uint256 i = 0; i < numSources; i++) {
takerTokenAmountsBySource[i] = _sampleBuySource(
sources[i],
takerToken,
makerToken,
makerTokenAmounts
);
}
}
/// @dev Sample sell quotes from Kyber.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromKyberNetwork(
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
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);
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(uint256 rate,) = _getKyberNetworkContract().getExpectedRate(
_takerToken,
_makerToken,
takerTokenAmounts[i]
);
makerTokenAmounts[i] =
rate *
takerTokenAmounts[i] *
10 ** makerTokenDecimals /
10 ** takerTokenDecimals /
10 ** 18;
}
}
/// @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 sampleSellsFromEth2Dai(
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount(
makerToken,
takerToken,
takerTokenAmounts[i]
);
}
}
/// @dev Sample buy quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromEth2Dai(
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount(
takerToken,
makerToken,
makerTokenAmounts[i]
);
}
}
/// @dev Sample sell quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswap(
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) {
makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice(
takerTokenAmounts[i]
);
} else if (takerToken == _getWETHAddress()) {
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
takerTokenAmounts[i]
);
} else {
uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice(
takerTokenAmounts[i]
);
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice(
ethBought
);
}
}
}
/// @dev Sample buy quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswap(
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) {
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
makerTokenAmounts[i]
);
} else if (takerToken == _getWETHAddress()) {
takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice(
makerTokenAmounts[i]
);
} else {
uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice(
makerTokenAmounts[i]
);
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice(
ethSold
);
}
}
}
/// @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).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function _sampleSellSource(
address source,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
private
view
returns (uint256[] memory makerTokenAmounts)
{
if (source == address(_getEth2DaiContract())) {
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
}
if (source == address(_getUniswapExchangeFactoryContract())) {
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
}
if (source == address(_getKyberNetworkContract())) {
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
}
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,
address makerToken,
uint256[] memory makerTokenAmounts
)
private
view
returns (uint256[] memory takerTokenAmounts)
{
if (source == address(_getEth2DaiContract())) {
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
}
if (source == address(_getUniswapExchangeFactoryContract())) {
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
}
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(_getUniswapExchangeFactoryContract().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
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");
}
function _assertValidPair(address makerToken, address takerToken)
private
pure
{
require(makerToken != takerToken, "INVALID_TOKEN_PAIR");
}
}

View File

@@ -0,0 +1,111 @@
/*
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-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.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(
LibOrder.Order[] calldata orders,
address[] calldata sources,
uint256[] calldata takerTokenAmounts
)
external
view
returns (
LibOrder.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(
LibOrder.Order[] calldata orders,
address[] calldata sources,
uint256[] calldata makerTokenAmounts
)
external
view
returns (
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(LibOrder.Order[] calldata orders)
external
view
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.
/// @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);
}

View File

@@ -0,0 +1,41 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
interface 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);
}

View File

@@ -0,0 +1,32 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
interface IKyberNetwork {
function getExpectedRate(
address fromToken,
address toToken,
uint256 fromAmount
)
external
view
returns (uint256 expectedRate, uint256 slippageRate);
}

View File

@@ -0,0 +1,51 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
interface IUniswapExchangeQuotes {
function getEthToTokenInputPrice(
uint256 ethSold
)
external
view
returns (uint256 tokensBought);
function getEthToTokenOutputPrice(
uint256 tokensBought
)
external
view
returns (uint256 ethSold);
function getTokenToEthInputPrice(
uint256 tokensSold
)
external
view
returns (uint256 ethBought);
function getTokenToEthOutputPrice(
uint256 ethBought
)
external
view
returns (uint256 tokensSold);
}

View File

@@ -0,0 +1,349 @@
/*
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 {
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,
address sellToken,
address buyToken,
uint256 sellAmount
)
internal
pure
returns (uint256 buyAmount)
{
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(
bytes32 salt,
address sellToken,
address buyToken,
uint256 buyAmount
)
internal
pure
returns (uint256 sellAmount)
{
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)
internal
pure
returns (uint8 decimals)
{
if (token == WETH_ADDRESS) {
return 18;
}
bytes32 seed = keccak256(abi.encodePacked(token));
return uint8(uint256(seed) % (MAX_DECIMALS - MIN_DECIMALS)) + MIN_DECIMALS;
}
function getDeterministicRate(bytes32 salt, address sellToken, address buyToken)
internal
pure
returns (uint256 rate)
{
bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken));
return uint256(seed) % (MAX_RATE - MIN_RATE) + MIN_RATE;
}
}
contract TestERC20BridgeSamplerUniswapExchange is
IUniswapExchangeQuotes,
DeploymentConstants
{
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
address public tokenAddress;
bytes32 public salt;
constructor(address _tokenAddress) public {
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,
tokenAddress,
WETH_ADDRESS,
ethSold
);
}
// Deterministic `IUniswapExchangeQuotes.getEthToTokenOutputPrice()`.
function getEthToTokenOutputPrice(
uint256 tokensBought
)
external
view
returns (uint256 ethSold)
{
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
WETH_ADDRESS,
tokenAddress,
tokensBought
);
}
// Deterministic `IUniswapExchangeQuotes.getTokenToEthInputPrice()`.
function getTokenToEthInputPrice(
uint256 tokensSold
)
external
view
returns (uint256 ethBought)
{
return LibDeterministicQuotes.getDeterministicSellQuote(
salt,
tokenAddress,
WETH_ADDRESS,
tokensSold
);
}
// Deterministic `IUniswapExchangeQuotes.getTokenToEthOutputPrice()`.
function getTokenToEthOutputPrice(
uint256 ethBought
)
external
view
returns (uint256 tokensSold)
{
return LibDeterministicQuotes.getDeterministicBuyQuote(
salt,
WETH_ADDRESS,
tokenAddress,
ethBought
);
}
}
contract TestERC20BridgeSamplerKyberNetwork is
IKyberNetwork,
DeploymentConstants
{
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// Deterministic `IKyberNetwork.getExpectedRate()`.
function getExpectedRate(
address fromToken,
address toToken,
uint256
)
external
view
returns (uint256 expectedRate, uint256)
{
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken;
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken;
expectedRate = LibDeterministicQuotes.getDeterministicRate(
SALT,
fromToken,
toToken
);
}
}
contract TestERC20BridgeSamplerEth2Dai is
IEth2Dai
{
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
// Deterministic `IEth2Dai.getBuyAmount()`.
function getBuyAmount(
address buyToken,
address payToken,
uint256 payAmount
)
external
view
returns (uint256 buyAmount)
{
return LibDeterministicQuotes.getDeterministicSellQuote(
SALT,
payToken,
buyToken,
payAmount
);
}
// Deterministic `IEth2Dai.getPayAmount()`.
function getPayAmount(
address payToken,
address buyToken,
uint256 buyAmount
)
external
view
returns (uint256 payAmount)
{
return LibDeterministicQuotes.getDeterministicBuyQuote(
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)
external
view
returns (address)
{
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
pure
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 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
returns (IExchange zeroex)
{
return IExchange(address(this));
}
// Overriden to point to a custom contract.
function _getEth2DaiContract()
internal
view
returns (IEth2Dai eth2dai_)
{
return eth2Dai;
}
// Overriden to point to a custom contract.
function _getUniswapExchangeFactoryContract()
internal
view
returns (IUniswapExchangeFactory uniswap_)
{
return uniswap;
}
// Overriden to point to a custom contract.
function _getKyberNetworkContract()
internal
view
returns (IKyberNetwork kyber_)
{
return kyber;
}
}

View File

@@ -0,0 +1,92 @@
{
"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,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|TestERC20BridgeSampler).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-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",
"@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"
}
}

View File

@@ -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,
};

View File

@@ -0,0 +1,2 @@
export * from './wrappers';
export * from './artifacts';

View File

@@ -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';

View File

@@ -0,0 +1,23 @@
/*
* -----------------------------------------------------------------------------
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
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 IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.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,
IKyberNetwork: IKyberNetwork as ContractArtifact,
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
};

View File

@@ -0,0 +1,852 @@
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';
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR';
before(async () => {
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
artifacts.TestERC20BridgeSampler,
env.provider,
env.txDefaults,
{},
);
allSources = _.zipObject(
['Uniswap', 'Eth2Dai', 'Kyber'],
[
await testContract.uniswap().callAsync(),
await testContract.eth2Dai().callAsync(),
await testContract.kyber().callAsync(),
],
);
});
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;
}
// 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(
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 areAddressesEqual(a: string, b: string): boolean {
return a.toLowerCase() === b.toLowerCase();
}
function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber {
if (areAddressesEqual(buyToken, WETH_ADDRESS)) {
return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount);
}
if (areAddressesEqual(sellToken, WETH_ADDRESS)) {
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 (areAddressesEqual(buyToken, WETH_ADDRESS)) {
return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount);
}
if (areAddressesEqual(sellToken, WETH_ADDRESS)) {
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]),
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]),
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]),
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]),
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]),
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]),
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]),
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]),
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('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([]);
});
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('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([]);
});
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('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([]);
});
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('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([]);
});
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();
return 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();
return 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('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([]);
});
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();
return 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();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR);
});
});
});

View File

@@ -0,0 +1,12 @@
/*
* -----------------------------------------------------------------------------
* 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_kyber_network';
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';

View File

@@ -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 <network-name>
*/
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: <address>, // 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 },
},
},
},
},
};

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"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/IKyberNetwork.json",
"test/generated-artifacts/IUniswapExchangeQuotes.json",
"test/generated-artifacts/TestERC20BridgeSampler.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}

View File

@@ -0,0 +1,10 @@
{
"extends": ["@0x/tslint-config"],
"rules": {
"custom-no-magic-numbers": false,
"max-file-line-count": false
},
"linterOptions": {
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]
}
}

View File

@@ -14,6 +14,10 @@
{ {
"note": "Drastically reduced bundle size by adding .npmignore, only exporting specific artifacts/wrappers/utils", "note": "Drastically reduced bundle size by adding .npmignore, only exporting specific artifacts/wrappers/utils",
"pr": 2330 "pr": 2330
},
{
"note": "Add `decimals()` to `LibERC20Token`.",
"pr": 2344
} }
], ],
"timestamp": 1574030254 "timestamp": 1574030254

View File

@@ -24,6 +24,7 @@ import "../src/interfaces/IERC20Token.sol";
library LibERC20Token { library LibERC20Token {
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
/// @dev Calls `IERC20Token(token).approve()`. /// @dev Calls `IERC20Token(token).approve()`.
/// Reverts if `false` is returned or if the return /// Reverts if `false` is returned or if the return
@@ -91,6 +92,21 @@ library LibERC20Token {
_callWithOptionalBooleanResult(token, callData); _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` /// @dev Executes a call on address `target` with calldata `callData`
/// and asserts that either nothing was returned or a single boolean /// and asserts that either nothing was returned or a single boolean
/// was returned equal to `true`. /// was returned equal to `true`.

View File

@@ -69,4 +69,16 @@ contract TestLibERC20Token {
target.setBehavior(shouldRevert, revertData, returnData); target.setBehavior(shouldRevert, revertData, returnData);
LibERC20Token.transferFrom(address(target), from, to, amount); 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));
}
} }

View File

@@ -87,6 +87,14 @@ contract TestLibERC20TokenTarget {
_execute(); _execute();
} }
function decimals()
external
view
returns (uint8)
{
_execute();
}
function _execute() private view { function _execute() private view {
if (_shouldRevert) { if (_shouldRevert) {
bytes memory revertData = _revertData; bytes memory revertData = _revertData;

View File

@@ -16,6 +16,7 @@ import { artifacts } from './artifacts';
blockchainTests('LibERC20Token', env => { blockchainTests('LibERC20Token', env => {
let testContract: TestLibERC20TokenContract; let testContract: TestLibERC20TokenContract;
const REVERT_STRING = 'WHOOPSIE'; const REVERT_STRING = 'WHOOPSIE';
const ENCODED_REVERT = new StringRevertError(REVERT_STRING).encode();
const ENCODED_TRUE = hexLeftPad(1); const ENCODED_TRUE = hexLeftPad(1);
const ENCODED_FALSE = hexLeftPad(0); const ENCODED_FALSE = hexLeftPad(0);
const ENCODED_TWO = hexLeftPad(2); const ENCODED_TWO = hexLeftPad(2);
@@ -31,16 +32,12 @@ blockchainTests('LibERC20Token', env => {
); );
}); });
function encodeRevert(message: string): string {
return new StringRevertError(message).encode();
}
describe('approve()', () => { describe('approve()', () => {
it('calls the target with the correct arguments', async () => { it('calls the target with the correct arguments', async () => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const { logs } = await testContract const { logs } = await testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
expect(logs).to.be.length(1); expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled); verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled);
@@ -50,7 +47,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
await testContract await testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -58,7 +55,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
await testContract await testContract
.testApprove(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, spender, allowance) .testApprove(false, ENCODED_REVERT, constants.NULL_BYTES, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -66,7 +63,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_FALSE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE); const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -76,7 +73,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_TWO, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_TWO, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO); const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -86,7 +83,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_SHORT_TRUE, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -96,7 +93,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testApprove(false, encodeRevert(REVERT_STRING), ENCODED_LONG_TRUE, spender, allowance) .testApprove(false, ENCODED_REVERT, ENCODED_LONG_TRUE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE); const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -106,7 +103,7 @@ blockchainTests('LibERC20Token', env => {
const spender = randomAddress(); const spender = randomAddress();
const allowance = getRandomInteger(0, 100e18); const allowance = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testApprove(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, spender, allowance) .testApprove(true, ENCODED_REVERT, ENCODED_TRUE, spender, allowance)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING); return expect(tx).to.revertWith(REVERT_STRING);
}); });
@@ -126,7 +123,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract const { logs } = await testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
expect(logs).to.be.length(1); expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled); verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled);
@@ -136,7 +133,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
await testContract await testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -144,7 +141,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
await testContract await testContract
.testTransfer(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, to, amount) .testTransfer(false, ENCODED_REVERT, constants.NULL_BYTES, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -152,7 +149,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_FALSE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE); const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -162,7 +159,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_TWO, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_TWO, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO); const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -172,7 +169,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_SHORT_TRUE, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_SHORT_TRUE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -182,7 +179,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransfer(false, encodeRevert(REVERT_STRING), ENCODED_LONG_TRUE, to, amount) .testTransfer(false, ENCODED_REVERT, ENCODED_LONG_TRUE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE); const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -192,7 +189,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransfer(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, to, amount) .testTransfer(true, ENCODED_REVERT, ENCODED_TRUE, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING); return expect(tx).to.revertWith(REVERT_STRING);
}); });
@@ -213,7 +210,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const { logs } = await testContract const { logs } = await testContract
.testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) .testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
expect(logs).to.be.length(1); expect(logs).to.be.length(1);
verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled); verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled);
@@ -224,7 +221,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
await testContract await testContract
.testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) .testTransferFrom(false, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -233,7 +230,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
await testContract await testContract
.testTransferFrom(false, encodeRevert(REVERT_STRING), constants.NULL_BYTES, owner, to, amount) .testTransferFrom(false, ENCODED_REVERT, constants.NULL_BYTES, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
}); });
@@ -242,7 +239,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_FALSE, owner, to, amount) .testTransferFrom(false, ENCODED_REVERT, ENCODED_FALSE, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_FALSE); const expectedError = new RawRevertError(ENCODED_FALSE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -253,7 +250,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransferFrom(false, encodeRevert(REVERT_STRING), ENCODED_TWO, owner, to, amount) .testTransferFrom(false, ENCODED_REVERT, ENCODED_TWO, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_TWO); const expectedError = new RawRevertError(ENCODED_TWO);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -264,7 +261,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract 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(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_SHORT_TRUE); const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -275,7 +272,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract 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(); .awaitTransactionSuccessAsync();
const expectedError = new RawRevertError(ENCODED_LONG_TRUE); const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -286,7 +283,7 @@ blockchainTests('LibERC20Token', env => {
const to = randomAddress(); const to = randomAddress();
const amount = getRandomInteger(0, 100e18); const amount = getRandomInteger(0, 100e18);
const tx = testContract const tx = testContract
.testTransferFrom(true, encodeRevert(REVERT_STRING), ENCODED_TRUE, owner, to, amount) .testTransferFrom(true, ENCODED_REVERT, ENCODED_TRUE, owner, to, amount)
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(REVERT_STRING); return expect(tx).to.revertWith(REVERT_STRING);
}); });
@@ -301,4 +298,39 @@ blockchainTests('LibERC20Token', env => {
return expect(tx).to.be.rejectedWith('revert'); 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);
});
});
}); });

View File

@@ -51,7 +51,7 @@
"lint:contracts": "wsrun lint -p ${npm_package_config_contractsPackages} -c --fast-exit --stages --exclude-missing" "lint:contracts": "wsrun lint -p ${npm_package_config_contractsPackages} -c --fast-exit --stages --exclude-missing"
}, },
"config": { "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", "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", "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", "ignoreDependencyVersions": "@types/styled-components @types/node",