Swallow reverts in ERC20BridgeSampler (#2395)

* `@0x/erc20-bridge-sampler`: Do not query empty/unsigned orders. Swallow revets on DEX quotes.

* `@0x/contracts-utils`: Add `DEV_UTILS_ADDRESS` and `KYBER_ETH_ADDRESS` to `DeploymentConstants`.

* `@0x/contracts-erc20-bridge-sampler`: Address review comments.
This commit is contained in:
Lawrence Forman 2019-12-13 10:53:25 -08:00 committed by GitHub
parent a556d91673
commit 70870ffcd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 718 additions and 312 deletions

View File

@ -1,4 +1,13 @@
[ [
{
"version": "1.0.2",
"changes": [
{
"note": "Do not query empty/unsigned orders. Swallow revets on DEX quotes.",
"pr": 2365
}
]
},
{ {
"timestamp": 1575931811, "timestamp": 1575931811,
"version": "1.0.1", "version": "1.0.1",

View File

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

@ -23,11 +23,13 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
import "./IDevUtils.sol";
import "./IERC20BridgeSampler.sol"; import "./IERC20BridgeSampler.sol";
import "./IEth2Dai.sol"; import "./IEth2Dai.sol";
import "./IKyberNetwork.sol"; import "./IKyberNetwork.sol";
import "./IUniswapExchangeQuotes.sol"; import "./IUniswapExchangeQuotes.sol";
import "./DeploymentConstants.sol";
contract ERC20BridgeSampler is contract ERC20BridgeSampler is
@ -38,26 +40,32 @@ contract ERC20BridgeSampler is
/// @dev Query native orders and sample sell quotes on multiple DEXes at once. /// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerTokenAmounts Taker token sell amount for each sample. /// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`. /// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at /// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample /// each taker token amount. First indexed by source index, then sample
/// index. /// index.
function queryOrdersAndSampleSells( function queryOrdersAndSampleSells(
LibOrder.Order[] memory orders, LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources, address[] memory sources,
uint256[] memory takerTokenAmounts uint256[] memory takerTokenAmounts
) )
public public
view view
returns ( returns (
LibOrder.OrderInfo[] memory orderInfos, uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource uint256[][] memory makerTokenAmountsBySource
) )
{ {
require(orders.length != 0, "EMPTY_ORDERS"); require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderInfos = queryOrders(orders); orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleSells( makerTokenAmountsBySource = sampleSells(
sources, sources,
_assetDataToTokenAddress(orders[0].takerAssetData), _assetDataToTokenAddress(orders[0].takerAssetData),
@ -68,26 +76,32 @@ contract ERC20BridgeSampler is
/// @dev Query native orders and sample buy quotes on multiple DEXes at once. /// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Maker token buy amount for each sample. /// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderInfos `OrderInfo`s for each order in `orders`. /// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at /// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample /// each maker token amount. First indexed by source index, then sample
/// index. /// index.
function queryOrdersAndSampleBuys( function queryOrdersAndSampleBuys(
LibOrder.Order[] memory orders, LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources, address[] memory sources,
uint256[] memory makerTokenAmounts uint256[] memory makerTokenAmounts
) )
public public
view view
returns ( returns (
LibOrder.OrderInfo[] memory orderInfos, uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource uint256[][] memory makerTokenAmountsBySource
) )
{ {
require(orders.length != 0, "EMPTY_ORDERS"); require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderInfos = queryOrders(orders); orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleBuys( makerTokenAmountsBySource = sampleBuys(
sources, sources,
_assetDataToTokenAddress(orders[0].takerAssetData), _assetDataToTokenAddress(orders[0].takerAssetData),
@ -96,18 +110,77 @@ contract ERC20BridgeSampler is
); );
} }
/// @dev Queries the status of several native orders. /// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// maker/taker asset amounts (returning 0).
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @return orderInfos Order info for each respective order. /// @param orderSignatures Signatures for each respective order in `orders`.
function queryOrders(LibOrder.Order[] memory orders) /// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getOrderFillableTakerAssetAmounts(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures
)
public public
view view
returns (LibOrder.OrderInfo[] memory orderInfos) returns (uint256[] memory orderFillableTakerAssetAmounts)
{ {
uint256 numOrders = orders.length; orderFillableTakerAssetAmounts = new uint256[](orders.length);
orderInfos = new LibOrder.OrderInfo[](numOrders); for (uint256 i = 0; i != orders.length; i++) {
for (uint256 i = 0; i < numOrders; i++) { // Ignore orders with no signature or empty maker/taker amounts.
orderInfos[i] = _getExchangeContract().getOrderInfo(orders[i]); if (orderSignatures[i].length == 0 ||
orders[i].makerAssetAmount == 0 ||
orders[i].takerAssetAmount == 0) {
orderFillableTakerAssetAmounts[i] = 0;
continue;
}
(
LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount,
bool isValidSignature
) = IDevUtils(_getDevUtilsAddress()).getOrderRelevantState(
orders[i],
orderSignatures[i]
);
// The fillable amount is zero if the order is not fillable or if the
// signature is invalid.
if (orderInfo.orderStatus != uint8(LibOrder.OrderStatus.FILLABLE) ||
!isValidSignature) {
orderFillableTakerAssetAmounts[i] = 0;
} else {
orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount;
}
}
}
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
function getOrderFillableMakerAssetAmounts(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures
)
public
view
returns (uint256[] memory orderFillableMakerAssetAmounts)
{
orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts(
orders,
orderSignatures
);
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
// convert them to maker asset amounts.
for (uint256 i = 0; i < orders.length; ++i) {
if (orderFillableMakerAssetAmounts[i] != 0) {
orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil(
orderFillableMakerAssetAmounts[i],
orders[i].takerAssetAmount,
orders[i].makerAssetAmount
);
}
} }
} }
@ -187,18 +260,24 @@ contract ERC20BridgeSampler is
returns (uint256[] memory makerTokenAmounts) returns (uint256[] memory makerTokenAmounts)
{ {
_assertValidPair(makerToken, takerToken); _assertValidPair(makerToken, takerToken);
address _takerToken = takerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : takerToken; address _takerToken = takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken;
address _makerToken = makerToken == _getWETHAddress() ? KYBER_ETH_ADDRESS : makerToken; address _makerToken = makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken;
uint256 takerTokenDecimals = _getTokenDecimals(takerToken); uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
uint256 makerTokenDecimals = _getTokenDecimals(makerToken); uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
uint256 numSamples = takerTokenAmounts.length; uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples); makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
(uint256 rate,) = _getKyberNetworkContract().getExpectedRate( (bool didSucceed, bytes memory resultData) =
_takerToken, _getKyberNetworkProxyAddress().staticcall(abi.encodeWithSelector(
_makerToken, IKyberNetwork(0).getExpectedRate.selector,
takerTokenAmounts[i] _takerToken,
); _makerToken,
takerTokenAmounts[i]
));
uint256 rate = 0;
if (didSucceed) {
rate = abi.decode(resultData, (uint256));
}
makerTokenAmounts[i] = makerTokenAmounts[i] =
rate * rate *
takerTokenAmounts[i] * takerTokenAmounts[i] *
@ -227,11 +306,18 @@ contract ERC20BridgeSampler is
uint256 numSamples = takerTokenAmounts.length; uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples); makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = _getEth2DaiContract().getBuyAmount( (bool didSucceed, bytes memory resultData) =
makerToken, _getEth2DaiAddress().staticcall(abi.encodeWithSelector(
takerToken, IEth2Dai(0).getBuyAmount.selector,
takerTokenAmounts[i] makerToken,
); takerToken,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
}
makerTokenAmounts[i] = buyAmount;
} }
} }
@ -254,11 +340,18 @@ contract ERC20BridgeSampler is
uint256 numSamples = makerTokenAmounts.length; uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples); takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = _getEth2DaiContract().getPayAmount( (bool didSucceed, bytes memory resultData) =
takerToken, _getEth2DaiAddress().staticcall(abi.encodeWithSelector(
makerToken, IEth2Dai(0).getPayAmount.selector,
makerTokenAmounts[i] takerToken,
); makerToken,
makerTokenAmounts[i]
));
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
}
takerTokenAmounts[i] = sellAmount;
} }
} }
@ -280,26 +373,38 @@ contract ERC20BridgeSampler is
_assertValidPair(makerToken, takerToken); _assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length; uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples); makerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) { if (makerToken == _getWethAddress()) {
makerTokenAmounts[i] = takerTokenExchange.getTokenToEthInputPrice( makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else if (takerToken == _getWETHAddress()) { } else if (takerToken == _getWethAddress()) {
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else { } else {
uint256 ethBought = takerTokenExchange.getTokenToEthInputPrice( uint256 ethBought = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
makerTokenAmounts[i] = makerTokenExchange.getEthToTokenInputPrice( if (ethBought != 0) {
ethBought makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
); address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
ethBought
);
} else {
makerTokenAmounts[i] = 0;
}
} }
} }
} }
@ -322,26 +427,38 @@ contract ERC20BridgeSampler is
_assertValidPair(makerToken, takerToken); _assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length; uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples); takerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWETHAddress() ? IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWETHAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
if (makerToken == _getWETHAddress()) { if (makerToken == _getWethAddress()) {
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else if (takerToken == _getWETHAddress()) { } else if (takerToken == _getWethAddress()) {
takerTokenAmounts[i] = makerTokenExchange.getEthToTokenOutputPrice( takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else { } else {
uint256 ethSold = makerTokenExchange.getEthToTokenOutputPrice( uint256 ethSold = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
takerTokenAmounts[i] = takerTokenExchange.getTokenToEthOutputPrice( if (ethSold != 0) {
ethSold takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
); address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
ethSold
);
} else {
takerTokenAmounts[i] = 0;
}
} }
} }
} }
@ -357,6 +474,34 @@ contract ERC20BridgeSampler is
return LibERC20Token.decimals(tokenAddress); return LibERC20Token.decimals(tokenAddress);
} }
/// @dev Gracefully calls a Uniswap pricing function.
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
/// @param functionSelector Selector of the target function.
/// @param inputAmount Quantity parameter particular to the pricing function.
/// @return outputAmount The returned amount from the function call. Will be
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
function _callUniswapExchangePriceFunction(
address uniswapExchangeAddress,
bytes4 functionSelector,
uint256 inputAmount
)
private
view
returns (uint256 outputAmount)
{
if (uniswapExchangeAddress == address(0)) {
return 0;
}
(bool didSucceed, bytes memory resultData) =
uniswapExchangeAddress.staticcall(abi.encodeWithSelector(
functionSelector,
inputAmount
));
if (didSucceed) {
outputAmount = abi.decode(resultData, (uint256));
}
}
/// @dev Samples a supported sell source, defined by its address. /// @dev Samples a supported sell source, defined by its address.
/// @param takerToken Address of the taker token (what to sell). /// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy). /// @param makerToken Address of the maker token (what to buy).
@ -373,16 +518,16 @@ contract ERC20BridgeSampler is
view view
returns (uint256[] memory makerTokenAmounts) returns (uint256[] memory makerTokenAmounts)
{ {
if (source == address(_getEth2DaiContract())) { if (source == _getEth2DaiAddress()) {
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts); return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
} }
if (source == address(_getUniswapExchangeFactoryContract())) { if (source == _getUniswapExchangeFactoryAddress()) {
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts); return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
} }
if (source == address(_getKyberNetworkContract())) { if (source == _getKyberNetworkProxyAddress()) {
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts); return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
} }
revert("UNSUPPORTED_SOURCE"); revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
} }
/// @dev Samples a supported buy source, defined by its address. /// @dev Samples a supported buy source, defined by its address.
@ -401,13 +546,13 @@ contract ERC20BridgeSampler is
view view
returns (uint256[] memory takerTokenAmounts) returns (uint256[] memory takerTokenAmounts)
{ {
if (source == address(_getEth2DaiContract())) { if (source == _getEth2DaiAddress()) {
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts); return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
} }
if (source == address(_getUniswapExchangeFactoryContract())) { if (source == _getUniswapExchangeFactoryAddress()) {
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts); return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
} }
revert("UNSUPPORTED_SOURCE"); revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
} }
/// @dev Retrive an existing Uniswap exchange contract. /// @dev Retrive an existing Uniswap exchange contract.
@ -420,9 +565,9 @@ contract ERC20BridgeSampler is
returns (IUniswapExchangeQuotes exchange) returns (IUniswapExchangeQuotes exchange)
{ {
exchange = IUniswapExchangeQuotes( exchange = IUniswapExchangeQuotes(
address(_getUniswapExchangeFactoryContract().getExchange(tokenAddress)) address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress())
.getExchange(tokenAddress))
); );
require(address(exchange) != address(0), "UNSUPPORTED_UNISWAP_EXCHANGE");
} }
/// @dev Extract the token address from ERC20 proxy asset data. /// @dev Extract the token address from ERC20 proxy asset data.
@ -433,19 +578,19 @@ contract ERC20BridgeSampler is
pure pure
returns (address tokenAddress) returns (address tokenAddress)
{ {
require(assetData.length == 36, "INVALID_ASSET_DATA"); require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA");
bytes4 selector; bytes4 selector;
assembly { assembly {
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
tokenAddress := mload(add(assetData, 0x24)) tokenAddress := mload(add(assetData, 0x24))
} }
require(selector == ERC20_PROXY_ID, "UNSUPPORTED_ASSET_PROXY"); require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY");
} }
function _assertValidPair(address makerToken, address takerToken) function _assertValidPair(address makerToken, address takerToken)
private private
pure pure
{ {
require(makerToken != takerToken, "INVALID_TOKEN_PAIR"); require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
} }
} }

View File

@ -0,0 +1,45 @@
/*
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-libs/contracts/src/LibOrder.sol";
interface IDevUtils {
/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
/// @param order The order structure.
/// @param signature Signature provided by maker that proves the order's authenticity.
/// `0x01` can always be provided if the signature does not need to be validated.
/// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order),
/// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state),
/// and isValidSignature (validity of the provided signature).
/// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled"
/// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final
/// amount of each asset that can be filled.
function getOrderRelevantState(LibOrder.Order calldata order, bytes calldata signature)
external
view
returns (
LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount,
bool isValidSignature
);
}

View File

@ -19,59 +19,82 @@
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
interface IERC20BridgeSampler { interface IERC20BridgeSampler {
/// @dev Query native orders and sample sell orders on multiple DEXes at once. /// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param sources Address of each DEX. Passing in an unknown DEX will throw. /// @param orderSignatures Signatures for each respective order in `orders`.
/// @param takerTokenAmounts Taker sell amount for each sample. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @return orderInfos `OrderInfo`s for each order in `orders`. /// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at /// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample /// each taker token amount. First indexed by source index, then sample
/// index. /// index.
function queryOrdersAndSampleSells( function queryOrdersAndSampleSells(
LibOrder.Order[] calldata orders, LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources, address[] calldata sources,
uint256[] calldata takerTokenAmounts uint256[] calldata takerTokenAmounts
) )
external external
view view
returns ( returns (
LibOrder.OrderInfo[] memory orderInfos, uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource uint256[][] memory makerTokenAmountsBySource
); );
/// @dev Query native orders and sample buy orders on multiple DEXes at once. /// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param sources Address of each DEX. Passing in an unknown DEX will throw. /// @param orderSignatures Signatures for each respective order in `orders`.
/// @param makerTokenAmounts Maker sell amount for each sample. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @return orderInfos `OrderInfo`s for each order in `orders`. /// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at /// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample /// each maker token amount. First indexed by source index, then sample
/// index. /// index.
function queryOrdersAndSampleBuys( function queryOrdersAndSampleBuys(
LibOrder.Order[] calldata orders, LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources, address[] calldata sources,
uint256[] calldata makerTokenAmounts uint256[] calldata makerTokenAmounts
) )
external external
view view
returns ( returns (
LibOrder.OrderInfo[] memory orderInfos, uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource uint256[][] memory makerTokenAmountsBySource
); );
/// @dev Queries the status of several native orders. /// @dev Queries the fillable taker asset amounts of native orders.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @return orderInfos Order info for each respective order. /// @param orderSignatures Signatures for each respective order in `orders`.
function queryOrders(LibOrder.Order[] calldata orders) /// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getOrderFillableTakerAssetAmounts(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures
)
external external
view view
returns (LibOrder.OrderInfo[] memory orderInfos); returns (uint256[] memory orderFillableTakerAssetAmounts);
/// @dev Queries the fillable maker asset amounts of native orders.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
function getOrderFillableMakerAssetAmounts(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures
)
external
view
returns (uint256[] memory orderFillableMakerAssetAmounts);
/// @dev Sample sell quotes on multiple DEXes at once. /// @dev Sample sell quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw. /// @param sources Address of each DEX. Passing in an unsupported DEX will throw.

View File

@ -23,6 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../src/ERC20BridgeSampler.sol"; import "../src/ERC20BridgeSampler.sol";
import "../src/IEth2Dai.sol"; import "../src/IEth2Dai.sol";
import "../src/IDevUtils.sol";
import "../src/IKyberNetwork.sol"; import "../src/IKyberNetwork.sol";
@ -90,9 +91,28 @@ library LibDeterministicQuotes {
} }
contract FailTrigger {
// Give this address a balance to force operations to fail.
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
// Funds `FAILURE_ADDRESS`.
function enableFailTrigger() external payable {
FAILURE_ADDRESS.transfer(msg.value);
}
function _revertIfShouldFail() internal view {
if (FAILURE_ADDRESS.balance != 0) {
revert("FAIL_TRIGGERED");
}
}
}
contract TestERC20BridgeSamplerUniswapExchange is contract TestERC20BridgeSamplerUniswapExchange is
IUniswapExchangeQuotes, IUniswapExchangeQuotes,
DeploymentConstants DeploymentConstants,
FailTrigger
{ {
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab; bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
@ -112,10 +132,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
view view
returns (uint256 tokensBought) returns (uint256 tokensBought)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote( return LibDeterministicQuotes.getDeterministicSellQuote(
salt, salt,
tokenAddress, tokenAddress,
WETH_ADDRESS, _getWethAddress(),
ethSold ethSold
); );
} }
@ -128,9 +149,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
view view
returns (uint256 ethSold) returns (uint256 ethSold)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote( return LibDeterministicQuotes.getDeterministicBuyQuote(
salt, salt,
WETH_ADDRESS, _getWethAddress(),
tokenAddress, tokenAddress,
tokensBought tokensBought
); );
@ -144,10 +166,11 @@ contract TestERC20BridgeSamplerUniswapExchange is
view view
returns (uint256 ethBought) returns (uint256 ethBought)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote( return LibDeterministicQuotes.getDeterministicSellQuote(
salt, salt,
tokenAddress, tokenAddress,
WETH_ADDRESS, _getWethAddress(),
tokensSold tokensSold
); );
} }
@ -160,9 +183,10 @@ contract TestERC20BridgeSamplerUniswapExchange is
view view
returns (uint256 tokensSold) returns (uint256 tokensSold)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote( return LibDeterministicQuotes.getDeterministicBuyQuote(
salt, salt,
WETH_ADDRESS, _getWethAddress(),
tokenAddress, tokenAddress,
ethBought ethBought
); );
@ -172,7 +196,8 @@ contract TestERC20BridgeSamplerUniswapExchange is
contract TestERC20BridgeSamplerKyberNetwork is contract TestERC20BridgeSamplerKyberNetwork is
IKyberNetwork, IKyberNetwork,
DeploymentConstants DeploymentConstants,
FailTrigger
{ {
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
@ -187,8 +212,9 @@ contract TestERC20BridgeSamplerKyberNetwork is
view view
returns (uint256 expectedRate, uint256) returns (uint256 expectedRate, uint256)
{ {
fromToken = fromToken == ETH_ADDRESS ? WETH_ADDRESS : fromToken; _revertIfShouldFail();
toToken = toToken == ETH_ADDRESS ? WETH_ADDRESS : toToken; fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
expectedRate = LibDeterministicQuotes.getDeterministicRate( expectedRate = LibDeterministicQuotes.getDeterministicRate(
SALT, SALT,
fromToken, fromToken,
@ -199,7 +225,8 @@ contract TestERC20BridgeSamplerKyberNetwork is
contract TestERC20BridgeSamplerEth2Dai is contract TestERC20BridgeSamplerEth2Dai is
IEth2Dai IEth2Dai,
FailTrigger
{ {
bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7; bytes32 constant private SALT = 0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7;
@ -213,6 +240,7 @@ contract TestERC20BridgeSamplerEth2Dai is
view view
returns (uint256 buyAmount) returns (uint256 buyAmount)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicSellQuote( return LibDeterministicQuotes.getDeterministicSellQuote(
SALT, SALT,
payToken, payToken,
@ -231,6 +259,7 @@ contract TestERC20BridgeSamplerEth2Dai is
view view
returns (uint256 payAmount) returns (uint256 payAmount)
{ {
_revertIfShouldFail();
return LibDeterministicQuotes.getDeterministicBuyQuote( return LibDeterministicQuotes.getDeterministicBuyQuote(
SALT, SALT,
payToken, payToken,
@ -269,7 +298,8 @@ contract TestERC20BridgeSamplerUniswapExchangeFactory is
contract TestERC20BridgeSampler is contract TestERC20BridgeSampler is
ERC20BridgeSampler ERC20BridgeSampler,
FailTrigger
{ {
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap; TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
TestERC20BridgeSamplerEth2Dai public eth2Dai; TestERC20BridgeSamplerEth2Dai public eth2Dai;
@ -288,18 +318,30 @@ contract TestERC20BridgeSampler is
uniswap.createTokenExchanges(tokenAddresses); uniswap.createTokenExchanges(tokenAddresses);
} }
// `IExchange.getOrderInfo()`, overridden to return deterministic order infos. // `IDevUtils.getOrderRelevantState()`, overridden to return deterministic
function getOrderInfo(LibOrder.Order memory order) // states.
function getOrderRelevantState(
LibOrder.Order memory order,
bytes memory
)
public public
pure view
returns (LibOrder.OrderInfo memory orderInfo) returns (
LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount,
bool isValidSignature
)
{ {
// The order hash is just the hash of the salt. // The order hash is just the hash of the salt.
bytes32 orderHash = keccak256(abi.encode(order.salt)); bytes32 orderHash = keccak256(abi.encode(order.salt));
// Everything else is derived from the hash. // Everything else is derived from the hash.
orderInfo.orderHash = orderHash; orderInfo.orderHash = orderHash;
orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1)); orderInfo.orderStatus = uint8(uint256(orderHash) % uint8(-1));
orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount; orderInfo.orderTakerAssetFilledAmount =
uint256(orderHash) % order.takerAssetAmount;
fillableTakerAssetAmount =
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;
isValidSignature = uint256(orderHash) % 2 == 1;
} }
// Overriden to return deterministic decimals. // Overriden to return deterministic decimals.
@ -312,38 +354,38 @@ contract TestERC20BridgeSampler is
} }
// Overriden to point to a this contract. // Overriden to point to a this contract.
function _getExchangeContract() function _getDevUtilsAddress()
internal internal
view view
returns (IExchange zeroex) returns (address devUtilAddress)
{ {
return IExchange(address(this)); return address(this);
} }
// Overriden to point to a custom contract. // Overriden to point to a custom contract.
function _getEth2DaiContract() function _getEth2DaiAddress()
internal internal
view view
returns (IEth2Dai eth2dai_) returns (address eth2daiAddress)
{ {
return eth2Dai; return address(eth2Dai);
} }
// Overriden to point to a custom contract. // Overriden to point to a custom contract.
function _getUniswapExchangeFactoryContract() function _getUniswapExchangeFactoryAddress()
internal internal
view view
returns (IUniswapExchangeFactory uniswap_) returns (address uniswapAddress)
{ {
return uniswap; return address(uniswap);
} }
// Overriden to point to a custom contract. // Overriden to point to a custom contract.
function _getKyberNetworkContract() function _getKyberNetworkProxyAddress()
internal internal
view view
returns (IKyberNetwork kyber_) returns (address kyberAddress)
{ {
return kyber; return address(kyber);
} }
} }

View File

@ -38,7 +38,7 @@
"config": { "config": {
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler", "publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "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" "abis": "./test/generated-artifacts/@(ERC20BridgeSampler|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -5,16 +5,16 @@
*/ */
import { ContractArtifact } from 'ethereum-types'; 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 ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json'; import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json'; import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
export const artifacts = { export const artifacts = {
DeploymentConstants: DeploymentConstants as ContractArtifact,
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
IDevUtils: IDevUtils as ContractArtifact,
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact, IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact,
IKyberNetwork: IKyberNetwork as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact,

View File

@ -6,7 +6,7 @@ import {
getRandomPortion, getRandomPortion,
randomAddress, randomAddress,
} from '@0x/contracts-test-utils'; } from '@0x/contracts-test-utils';
import { Order, OrderInfo } from '@0x/types'; import { Order } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils'; import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -30,12 +30,13 @@ blockchainTests('erc20-bridge-sampler', env => {
const INVALID_ASSET_DATA = hexUtils.random(37); const INVALID_ASSET_DATA = hexUtils.random(37);
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap']; const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
const BUY_SOURCES = ['Eth2Dai', 'Uniswap']; const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS'; const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY'; const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA'; const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE'; const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE'; const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR'; const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
@ -192,13 +193,22 @@ blockchainTests('erc20-bridge-sampler', env => {
return quotes; return quotes;
} }
function getDeterministicOrderInfo(order: Order): OrderInfo { function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
const hash = getPackedHash(hexUtils.leftPad(order.salt, 32)); const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
return { const orderStatus = new BigNumber(hash).mod(255).toNumber();
orderHash: hash, const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
orderStatus: new BigNumber(hash).mod(255).toNumber(), if (orderStatus !== 3 || !isValidSignature) {
orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount), return constants.ZERO_AMOUNT;
}; }
return order.takerAssetAmount.minus(new BigNumber(hash).mod(order.takerAssetAmount));
}
function getDeterministicFillableMakerAssetAmount(order: Order): BigNumber {
const takerAmount = getDeterministicFillableTakerAssetAmount(order);
return order.makerAssetAmount
.times(takerAmount)
.div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_DOWN);
} }
function getERC20AssetData(tokenAddress: string): string { function getERC20AssetData(tokenAddress: string): string {
@ -238,57 +248,115 @@ blockchainTests('erc20-bridge-sampler', env => {
return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken));
} }
describe('queryOrders()', () => { async function enableFailTriggerAsync(): Promise<void> {
const MAKER_TOKEN = randomAddress(); await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
const TAKER_TOKEN = randomAddress(); }
it('returns the results of `getOrderInfo()` for each order', async () => { describe('getOrderFillableTakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const expected = orders.map(getDeterministicOrderInfo); const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.queryOrders(orders).callAsync(); const expected = orders.map(getDeterministicFillableTakerAssetAmount);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected); expect(actual).to.deep.eq(expected);
}); });
it('returns empty for no orders', async () => { it('returns empty for no orders', async () => {
const actual = await testContract.queryOrders([]).callAsync(); const actual = await testContract.getOrderFillableTakerAssetAmounts([], []).callAsync();
expect(actual).to.deep.eq([]); expect(actual).to.deep.eq([]);
}); });
it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with an empty signature', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
});
describe('getOrderFillableMakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const signatures: string[] = _.times(orders.length, hexUtils.random);
const expected = orders.map(getDeterministicFillableMakerAssetAmount);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected);
});
it('returns empty for no orders', async () => {
const actual = await testContract.getOrderFillableMakerAssetAmounts([], []).callAsync();
expect(actual).to.deep.eq([]);
});
it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
it('returns zero for an order with an empty signature', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
const signatures: string[] = _.times(orders.length, () => constants.NULL_BYTES);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
});
}); });
describe('queryOrdersAndSampleSells()', () => { describe('queryOrdersAndSampleSells()', () => {
const MAKER_TOKEN = randomAddress(); const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const TAKER_TOKEN = randomAddress(); const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
it('returns the results of `getOrderInfo()` for each order', async () => { it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN); const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
const [orderInfos] = await testContract const [orderInfos] = await testContract
.queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts) .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.callAsync(); .callAsync();
expect(orderInfos).to.deep.eq(expectedOrderInfos); expect(orderInfos).to.deep.eq(expectedFillableAmounts);
}); });
it('can return quotes for all sources', async () => { it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
const [, quotes] = await testContract const [, quotes] = await testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => allSources[n]), sampleAmounts)
createOrders(MAKER_TOKEN, TAKER_TOKEN),
SELL_SOURCES.map(n => allSources[n]),
sampleAmounts,
)
.callAsync(); .callAsync();
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no orders are passed in', async () => { it('throws if no orders are passed in', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN)) .queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN))
.callAsync(); .callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
}); });
@ -296,7 +364,8 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with an unsupported source', async () => { it('throws with an unsupported source', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN), ORDERS,
SIGNATURES,
[...SELL_SOURCES.map(n => allSources[n]), randomAddress()], [...SELL_SOURCES.map(n => allSources[n]), randomAddress()],
getSampleAmounts(TAKER_TOKEN), getSampleAmounts(TAKER_TOKEN),
) )
@ -307,10 +376,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 maker asset data', async () => { it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})), })),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]), SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN), getSampleAmounts(TAKER_TOKEN),
) )
@ -321,10 +391,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 taker asset data', async () => { it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})), })),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]), SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN), getSampleAmounts(TAKER_TOKEN),
) )
@ -335,10 +406,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid maker asset data', async () => { it('throws with invalid maker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
makerAssetData: INVALID_ASSET_DATA, makerAssetData: INVALID_ASSET_DATA,
})), })),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]), SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN), getSampleAmounts(TAKER_TOKEN),
) )
@ -349,10 +421,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid taker asset data', async () => { it('throws with invalid taker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleSells( .queryOrdersAndSampleSells(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
takerAssetData: INVALID_ASSET_DATA, takerAssetData: INVALID_ASSET_DATA,
})), })),
SIGNATURES,
SELL_SOURCES.map(n => allSources[n]), SELL_SOURCES.map(n => allSources[n]),
getSampleAmounts(TAKER_TOKEN), getSampleAmounts(TAKER_TOKEN),
) )
@ -362,39 +435,34 @@ blockchainTests('erc20-bridge-sampler', env => {
}); });
describe('queryOrdersAndSampleBuys()', () => { describe('queryOrdersAndSampleBuys()', () => {
const MAKER_TOKEN = randomAddress(); const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const TAKER_TOKEN = randomAddress(); const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
it('returns the results of `getOrderInfo()` for each order', async () => { it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN); const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
const expectedOrderInfos = orders.map(getDeterministicOrderInfo);
const [orderInfos] = await testContract const [orderInfos] = await testContract
.queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts) .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts)
.callAsync(); .callAsync();
expect(orderInfos).to.deep.eq(expectedOrderInfos); expect(orderInfos).to.deep.eq(expectedFillableAmounts);
}); });
it('can return quotes for all sources', async () => { it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
const [, quotes] = await testContract const [, quotes] = await testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => allSources[n]), sampleAmounts)
createOrders(MAKER_TOKEN, TAKER_TOKEN),
BUY_SOURCES.map(n => allSources[n]),
sampleAmounts,
)
.callAsync(); .callAsync();
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no orders are passed in', async () => { it('throws if no orders are passed in', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN)) .queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN))
.callAsync(); .callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
}); });
@ -402,7 +470,8 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with an unsupported source', async () => { it('throws with an unsupported source', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN), ORDERS,
SIGNATURES,
[...BUY_SOURCES.map(n => allSources[n]), randomAddress()], [...BUY_SOURCES.map(n => allSources[n]), randomAddress()],
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -414,7 +483,8 @@ blockchainTests('erc20-bridge-sampler', env => {
const sources = [...BUY_SOURCES, 'Kyber']; const sources = [...BUY_SOURCES, 'Kyber'];
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN), ORDERS,
SIGNATURES,
sources.map(n => allSources[n]), sources.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -425,10 +495,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 maker asset data', async () => { it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})), })),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]), BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -439,10 +510,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with non-ERC20 taker asset data', async () => { it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})), })),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]), BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -453,10 +525,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid maker asset data', async () => { it('throws with invalid maker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
makerAssetData: INVALID_ASSET_DATA, makerAssetData: INVALID_ASSET_DATA,
})), })),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]), BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -467,10 +540,11 @@ blockchainTests('erc20-bridge-sampler', env => {
it('throws with invalid taker asset data', async () => { it('throws with invalid taker asset data', async () => {
const tx = testContract const tx = testContract
.queryOrdersAndSampleBuys( .queryOrdersAndSampleBuys(
createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ORDERS.map(o => ({
...o, ...o,
takerAssetData: INVALID_ASSET_DATA, takerAssetData: INVALID_ASSET_DATA,
})), })),
SIGNATURES,
BUY_SOURCES.map(n => allSources[n]), BUY_SOURCES.map(n => allSources[n]),
getSampleAmounts(MAKER_TOKEN), getSampleAmounts(MAKER_TOKEN),
) )
@ -480,9 +554,6 @@ blockchainTests('erc20-bridge-sampler', env => {
}); });
describe('sampleSells()', () => { describe('sampleSells()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -528,9 +599,6 @@ blockchainTests('erc20-bridge-sampler', env => {
}); });
describe('sampleBuys()', () => { describe('sampleBuys()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -583,10 +651,7 @@ blockchainTests('erc20-bridge-sampler', env => {
}); });
}); });
describe('sampleSellsFromKyberNetwork()', () => { blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -601,7 +666,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]); expect(quotes).to.deep.eq([]);
}); });
it('can return many quotes', async () => { it('can quote token - token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
const quotes = await testContract const quotes = await testContract
@ -610,6 +675,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => { it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
@ -619,6 +694,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => { it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
@ -627,12 +712,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync(); .callAsync();
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
}); });
describe('sampleSellsFromEth2Dai()', () => { blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -647,7 +739,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]); expect(quotes).to.deep.eq([]);
}); });
it('can return many quotes', async () => { it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
const quotes = await testContract const quotes = await testContract
@ -656,6 +748,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => { it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
@ -665,6 +767,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => { it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
@ -673,12 +785,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync(); .callAsync();
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
}); });
describe('sampleBuysFromEth2Dai()', () => { blockchainTests.resets('sampleBuysFromEth2Dai()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -693,7 +812,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]); expect(quotes).to.deep.eq([]);
}); });
it('can return many quotes', async () => { it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
const quotes = await testContract const quotes = await testContract
@ -702,6 +821,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => { it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts);
@ -711,6 +840,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => { it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts);
@ -719,12 +858,19 @@ blockchainTests('erc20-bridge-sampler', env => {
.callAsync(); .callAsync();
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if ETH -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
}); });
describe('sampleSellsFromUniswap()', () => { blockchainTests.resets('sampleSellsFromUniswap()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -739,7 +885,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]); expect(quotes).to.deep.eq([]);
}); });
it('can return many quotes', async () => { it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
const quotes = await testContract const quotes = await testContract
@ -748,6 +894,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => { it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
@ -757,6 +913,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => { it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
@ -766,27 +932,38 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no exchange exists for the maker token', async () => { it('returns zero if ETH -> token fails', async () => {
const nonExistantToken = randomAddress(); const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const tx = testContract const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) await enableFailTriggerAsync();
const quotes = await testContract
.sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync(); .callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no exchange exists for the taker token', async () => { it('returns zero if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress(); const nonExistantToken = randomAddress();
const tx = testContract const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
.callAsync(); .callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if no exchange exists for the taker token', async () => {
const nonExistantToken = randomAddress();
const sampleAmounts = getSampleAmounts(nonExistantToken);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
}); });
}); });
describe('sampleBuysFromUniswap()', () => { blockchainTests.resets('sampleBuysFromUniswap()', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
}); });
@ -801,7 +978,7 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq([]); expect(quotes).to.deep.eq([]);
}); });
it('can return many quotes', async () => { it('can quote token -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts);
const quotes = await testContract const quotes = await testContract
@ -810,6 +987,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> token fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote token -> ETH', async () => { it('can quote token -> ETH', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts);
@ -819,6 +1006,16 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('returns zero if token -> ETH fails', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can quote ETH -> token', async () => { it('can quote ETH -> token', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts);
@ -828,20 +1025,34 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no exchange exists for the maker token', async () => { it('returns zero if ETH -> token fails', async () => {
const nonExistantToken = randomAddress(); const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const tx = testContract const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) await enableFailTriggerAsync();
const quotes = await testContract
.sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
.callAsync(); .callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); expect(quotes).to.deep.eq(expectedQuotes);
}); });
it('throws if no exchange exists for the taker token', async () => { it('returns zero if no exchange exists for the maker token', async () => {
const nonExistantToken = randomAddress(); const nonExistantToken = randomAddress();
const tx = testContract const sampleAmounts = getSampleAmounts(nonExistantToken);
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, sampleAmounts)
.callAsync(); .callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); expect(quotes).to.deep.eq(expectedQuotes);
});
it('returns zero if no exchange exists for the taker token', async () => {
const nonExistantToken = randomAddress();
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
const quotes = await testContract
.sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
}); });
}); });
}); });

View File

@ -3,8 +3,8 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * 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/erc20_bridge_sampler';
export * from '../test/generated-wrappers/i_dev_utils';
export * from '../test/generated-wrappers/i_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_eth2_dai';
export * from '../test/generated-wrappers/i_kyber_network'; export * from '../test/generated-wrappers/i_kyber_network';

View File

@ -5,8 +5,8 @@
"files": [ "files": [
"generated-artifacts/ERC20BridgeSampler.json", "generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/IERC20BridgeSampler.json", "generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/DeploymentConstants.json",
"test/generated-artifacts/ERC20BridgeSampler.json", "test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/IDevUtils.json",
"test/generated-artifacts/IERC20BridgeSampler.json", "test/generated-artifacts/IERC20BridgeSampler.json",
"test/generated-artifacts/IEth2Dai.json", "test/generated-artifacts/IEth2Dai.json",
"test/generated-artifacts/IKyberNetwork.json", "test/generated-artifacts/IKyberNetwork.json",

View File

@ -1,4 +1,13 @@
[ [
{
"version": "4.0.2",
"changes": [
{
"note": "Add `DEV_UTILS_ADDRESS` and `KYBER_ETH_ADDRESS` to `DeploymentConstants`.",
"pr": 2365
}
]
},
{ {
"timestamp": 1575931811, "timestamp": 1575931811,
"version": "4.0.1", "version": "4.0.1",

View File

@ -38,6 +38,10 @@ contract DeploymentConstants {
address constant private DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address constant private DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
/// @dev Mainnet address of the `Chai` contract /// @dev Mainnet address of the `Chai` contract
address constant private CHAI_ADDRESS = 0x06AF07097C9Eeb7fD685c692751D5C66dB49c215; address constant private CHAI_ADDRESS = 0x06AF07097C9Eeb7fD685c692751D5C66dB49c215;
/// @dev Address of the 0x DevUtils contract.
address constant private DEV_UTILS_ADDRESS = 0xcCc2431a7335F21d9268bA62F0B32B0f2EFC463f;
/// @dev Kyber ETH pseudo-address.
address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Overridable way to get the `KyberNetworkProxy` address. /// @dev Overridable way to get the `KyberNetworkProxy` address.
/// @return kyberAddress The `IKyberNetworkProxy` address. /// @return kyberAddress The `IKyberNetworkProxy` address.
@ -108,4 +112,14 @@ contract DeploymentConstants {
{ {
return CHAI_ADDRESS; return CHAI_ADDRESS;
} }
/// @dev An overridable way to retrieve the 0x `DevUtils` contract address.
/// @return devUtils The 0x `DevUtils` contract address.
function _getDevUtilsAddress()
internal
view
returns (address devUtils)
{
return DEV_UTILS_ADDRESS;
}
} }