@0x/contracts-erc20-bridge-sampler: Remove wrapper functions and introduce batchCall().

This commit is contained in:
Lawrence Forman
2020-02-07 20:22:00 -05:00
committed by Lawrence Forman
parent 1b83ebdf89
commit 6273a1ca73
5 changed files with 155 additions and 718 deletions

View File

@@ -5,6 +5,10 @@
{ {
"note": "Catch reverts to `DevUtils` calls", "note": "Catch reverts to `DevUtils` calls",
"pr": 2476 "pr": 2476
},
{
"note": "Remove wrapper functions and introduce `batchCall()`",
"pr": 2477
} }
], ],
"timestamp": 1581204851 "timestamp": 1581204851

View File

@@ -21,7 +21,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol";
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-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-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
@@ -36,154 +35,36 @@ contract ERC20BridgeSampler is
IERC20BridgeSampler, IERC20BridgeSampler,
DeploymentConstants DeploymentConstants
{ {
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); /// @dev Gas limit for DevUtils calls.
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3; uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3; /// @dev Gas limit for Kyber calls.
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3; uint256 constant internal KYBER_CALL_GAS = 1500e3; // 1.5m
uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; /// @dev Gas limit for Uniswap calls.
address constant private UNISWAP_SOURCE = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; uint256 constant internal UNISWAP_CALL_GAS = 150e3; // 150k
address constant private ETH2DAI_SOURCE = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e; /// @dev Base gas limit for Eth2Dai calls.
address constant private KYBER_SOURCE = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; uint256 constant internal ETH2DAI_CALL_GAS = 1000e3; // 1m
/// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. /// @dev Call multiple public functions on this contract in a single transaction.
/// @param orders Batches of Native orders to query. /// @param callDatas ABI-encoded call data for each function call.
/// @param orderSignatures Batches of Signatures for each respective order in `orders`. /// @return callResults ABI-encoded results data for each call.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw. function batchCall(bytes[] calldata callDatas)
/// @param takerTokenAmounts Batches of Taker token sell amount for each sample. external
/// @return ordersAndSamples How much taker asset can be filled
/// by each order in `orders`. Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryBatchOrdersAndSampleSells(
LibOrder.Order[][] memory orders,
bytes[][] memory orderSignatures,
address[] memory sources,
uint256[][] memory takerTokenAmounts
)
public
view view
returns ( returns (bytes[] memory callResults)
OrdersAndSample[] memory ordersAndSamples
)
{ {
ordersAndSamples = new OrdersAndSample[](orders.length); callResults = new bytes[](callDatas.length);
for (uint256 i = 0; i != orders.length; i++) { for (uint256 i = 0; i != callDatas.length; ++i) {
( (bool didSucceed, bytes memory resultData) = address(this).staticcall(callDatas[i]);
uint256[] memory orderFillableAssetAmounts, if (!didSucceed) {
uint256[][] memory tokenAmountsBySource assembly { revert(add(resultData, 0x20), mload(resultData)) }
) = queryOrdersAndSampleSells(orders[i], orderSignatures[i], sources, takerTokenAmounts[i]); }
ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts; callResults[i] = resultData;
ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource;
} }
} }
/// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Batches of Native orders to query.
/// @param orderSignatures Batches of Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Batches of Maker token sell amount for each sample.
/// @return ordersAndSamples How much taker asset can be filled
/// by each order in `orders`. Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function queryBatchOrdersAndSampleBuys(
LibOrder.Order[][] memory orders,
bytes[][] memory orderSignatures,
address[] memory sources,
uint256[][] memory makerTokenAmounts
)
public
view
returns (
OrdersAndSample[] memory ordersAndSamples
)
{
ordersAndSamples = new OrdersAndSample[](orders.length);
for (uint256 i = 0; i != orders.length; i++) {
(
uint256[] memory orderFillableAssetAmounts,
uint256[][] memory tokenAmountsBySource
) = queryOrdersAndSampleBuys(orders[i], orderSignatures[i], sources, makerTokenAmounts[i]);
ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts;
ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource;
}
}
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleSells(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources,
uint256[] memory takerTokenAmounts
)
public
view
returns (
uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
)
{
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderFillableTakerAssetAmounts = getOrderFillableTakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleSells(
sources,
_assetDataToTokenAddress(orders[0].takerAssetData),
_assetDataToTokenAddress(orders[0].makerAssetData),
takerTokenAmounts
);
}
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleBuys(
LibOrder.Order[] memory orders,
bytes[] memory orderSignatures,
address[] memory sources,
uint256[] memory makerTokenAmounts
)
public
view
returns (
uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
)
{
require(orders.length != 0, "ERC20BridgeSampler/EMPTY_ORDERS");
orderFillableMakerAssetAmounts = getOrderFillableMakerAssetAmounts(
orders,
orderSignatures
);
makerTokenAmountsBySource = sampleBuys(
sources,
_assetDataToTokenAddress(orders[0].takerAssetData),
_assetDataToTokenAddress(orders[0].makerAssetData),
makerTokenAmounts
);
}
/// @dev Queries the fillable taker asset amounts of native orders. /// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or /// Effectively ignores orders that have empty signatures or
/// maker/taker asset amounts (returning 0). /// maker/taker asset amounts (returning 0).
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`. /// @param orderSignatures Signatures for each respective order in `orders`.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled /// @return orderFillableTakerAssetAmounts How much taker asset can be filled
@@ -270,66 +151,6 @@ contract ERC20BridgeSampler is
} }
} }
/// @dev Sample sell quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function sampleSells(
address[] memory sources,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[][] memory makerTokenAmountsBySource)
{
uint256 numSources = sources.length;
makerTokenAmountsBySource = new uint256[][](numSources);
for (uint256 i = 0; i < numSources; i++) {
makerTokenAmountsBySource[i] = _sampleSellSource(
sources[i],
takerToken,
makerToken,
takerTokenAmounts
);
}
}
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function sampleBuys(
address[] memory sources,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[][] memory takerTokenAmountsBySource)
{
uint256 numSources = sources.length;
takerTokenAmountsBySource = new uint256[][](numSources);
for (uint256 i = 0; i < numSources; i++) {
takerTokenAmountsBySource[i] = _sampleBuySource(
sources[i],
takerToken,
makerToken,
makerTokenAmounts
);
}
}
/// @dev Sample sell quotes from Kyber. /// @dev Sample sell quotes from Kyber.
/// @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).
@@ -354,7 +175,7 @@ contract ERC20BridgeSampler is
makerTokenAmounts = new uint256[](numSamples); makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) = (bool didSucceed, bytes memory resultData) =
_getKyberNetworkProxyAddress().staticcall.gas(KYBER_SAMPLE_CALL_GAS)( _getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
IKyberNetwork(0).getExpectedRate.selector, IKyberNetwork(0).getExpectedRate.selector,
_takerToken, _takerToken,
@@ -396,7 +217,7 @@ contract ERC20BridgeSampler is
makerTokenAmounts = new uint256[](numSamples); makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) = (bool didSucceed, bytes memory resultData) =
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
IEth2Dai(0).getBuyAmount.selector, IEth2Dai(0).getBuyAmount.selector,
makerToken, makerToken,
@@ -433,7 +254,7 @@ contract ERC20BridgeSampler is
takerTokenAmounts = new uint256[](numSamples); takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) = (bool didSucceed, bytes memory resultData) =
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)( _getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
IEth2Dai(0).getPayAmount.selector, IEth2Dai(0).getPayAmount.selector,
takerToken, takerToken,
@@ -599,7 +420,7 @@ contract ERC20BridgeSampler is
} }
bytes memory resultData; bytes memory resultData;
(didSucceed, resultData) = (didSucceed, resultData) =
uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)( uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
functionSelector, functionSelector,
inputAmount inputAmount
@@ -609,59 +430,6 @@ contract ERC20BridgeSampler is
} }
} }
/// @dev Samples a supported sell source, defined by its address.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function _sampleSellSource(
address source,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
private
view
returns (uint256[] memory makerTokenAmounts)
{
if (source == ETH2DAI_SOURCE) {
return sampleSellsFromEth2Dai(takerToken, makerToken, takerTokenAmounts);
}
if (source == UNISWAP_SOURCE) {
return sampleSellsFromUniswap(takerToken, makerToken, takerTokenAmounts);
}
if (source == KYBER_SOURCE) {
return sampleSellsFromKyberNetwork(takerToken, makerToken, takerTokenAmounts);
}
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
}
/// @dev Samples a supported buy source, defined by its address.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function _sampleBuySource(
address source,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
private
view
returns (uint256[] memory takerTokenAmounts)
{
if (source == ETH2DAI_SOURCE) {
return sampleBuysFromEth2Dai(takerToken, makerToken, makerTokenAmounts);
}
if (source == UNISWAP_SOURCE) {
return sampleBuysFromUniswap(takerToken, makerToken, makerTokenAmounts);
}
revert("ERC20BridgeSampler/UNSUPPORTED_SOURCE");
}
/// @dev Retrive an existing Uniswap exchange contract. /// @dev Retrive an existing Uniswap exchange contract.
/// Throws if the exchange does not exist. /// Throws if the exchange does not exist.
/// @param tokenAddress Address of the token contract. /// @param tokenAddress Address of the token contract.
@@ -677,23 +445,9 @@ contract ERC20BridgeSampler is
); );
} }
/// @dev Extract the token address from ERC20 proxy asset data. /// @dev Assert that the tokens in a trade pair are valid.
/// @param assetData ERC20 asset data. /// @param makerToken Address of the maker token.
/// @return tokenAddress The decoded token address. /// @param takerToken Address of the taker token.
function _assetDataToTokenAddress(bytes memory assetData)
private
pure
returns (address tokenAddress)
{
require(assetData.length == 36, "ERC20BridgeSampler/INVALID_ASSET_DATA");
bytes4 selector;
assembly {
selector := and(mload(add(assetData, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
tokenAddress := mload(add(assetData, 0x24))
}
require(selector == ERC20_PROXY_ID, "ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY");
}
function _assertValidPair(address makerToken, address takerToken) function _assertValidPair(address makerToken, address takerToken)
private private
pure pure

View File

@@ -23,98 +23,14 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
interface IERC20BridgeSampler { interface IERC20BridgeSampler {
struct OrdersAndSample {
uint256[] orderFillableAssetAmounts;
uint256[][] tokenAmountsBySource;
}
/// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. /// @dev Call multiple public functions on this contract in a single transaction.
/// @param orders Batches of Native orders to query. /// @param callDatas ABI-encoded call data for each function call.
/// @param orderSignatures Batches of Signatures for each respective order in `orders`. /// @return callResults ABI-encoded results data for each call.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw. function batchCall(bytes[] calldata callDatas)
/// @param takerTokenAmounts Batches of Taker token sell amount for each sample.
/// @return ordersAndSamples How much taker asset can be filled
/// by each order in `orders`. Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryBatchOrdersAndSampleSells(
LibOrder.Order[][] calldata orders,
bytes[][] calldata orderSignatures,
address[] calldata sources,
uint256[][] calldata takerTokenAmounts
)
external external
view view
returns ( returns (bytes[] memory callResults);
OrdersAndSample[] memory ordersAndSamples
);
/// @dev Query batches of native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Batches of Native orders to query.
/// @param orderSignatures Batches of Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Batches of Maker token sell amount for each sample.
/// @return ordersAndSamples How much taker asset can be filled
/// by each order in `orders`. Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index
function queryBatchOrdersAndSampleBuys(
LibOrder.Order[][] calldata orders,
bytes[][] calldata orderSignatures,
address[] calldata sources,
uint256[][] calldata makerTokenAmounts
)
external
view
returns (
OrdersAndSample[] memory ordersAndSamples
);
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
/// each taker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleSells(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata takerTokenAmounts
)
external
view
returns (
uint256[] memory orderFillableTakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
);
/// @dev Query native orders and sample buy quotes on multiple DEXes at once.
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
/// each maker token amount. First indexed by source index, then sample
/// index.
function queryOrdersAndSampleBuys(
LibOrder.Order[] calldata orders,
bytes[] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata makerTokenAmounts
)
external
view
returns (
uint256[] memory orderFillableMakerAssetAmounts,
uint256[][] memory makerTokenAmountsBySource
);
/// @dev Queries the fillable taker asset amounts of native orders. /// @dev Queries the fillable taker asset amounts of native orders.
/// @param orders Native orders to query. /// @param orders Native orders to query.
@@ -142,39 +58,78 @@ interface IERC20BridgeSampler {
view view
returns (uint256[] memory orderFillableMakerAssetAmounts); returns (uint256[] memory orderFillableMakerAssetAmounts);
/// @dev Sample sell quotes on multiple DEXes at once. /// @dev Sample sell quotes from Kyber.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @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).
/// @param takerTokenAmounts Taker token sell amount for each sample. /// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmountsBySource Maker amounts bought for each source at /// @return makerTokenAmounts Maker amounts bought at each taker token
/// each taker token amount. First indexed by source index, then sample /// amount.
/// index. function sampleSellsFromKyberNetwork(
function sampleSells(
address[] calldata sources,
address takerToken, address takerToken,
address makerToken, address makerToken,
uint256[] calldata takerTokenAmounts uint256[] calldata takerTokenAmounts
) )
external external
view view
returns (uint256[][] memory makerTokenAmountsBySource); returns (uint256[] memory makerTokenAmounts);
/// @dev Query native orders and sample buy quotes on multiple DEXes at once. /// @dev Sample sell quotes from Eth2Dai/Oasis.
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
/// @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).
/// @param makerTokenAmounts Maker token buy amount for each sample. /// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return takerTokenAmountsBySource Taker amounts sold for each source at /// @return makerTokenAmounts Maker amounts bought at each taker token
/// each maker token amount. First indexed by source index, then sample /// amount.
/// index. function sampleSellsFromEth2Dai(
function sampleBuys( address takerToken,
address[] calldata sources, address makerToken,
uint256[] calldata takerTokenAmounts
)
external
view
returns (uint256[] memory makerTokenAmounts);
/// @dev Sample sell quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswap(
address takerToken,
address makerToken,
uint256[] calldata takerTokenAmounts
)
external
view
returns (uint256[] memory makerTokenAmounts);
/// @dev Sample buy quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswap(
address takerToken, address takerToken,
address makerToken, address makerToken,
uint256[] calldata makerTokenAmounts uint256[] calldata makerTokenAmounts
) )
external external
view view
returns (uint256[][] memory takerTokenAmountsBySource); returns (uint256[] memory takerTokenAmounts);
/// @dev Sample buy quotes from Eth2Dai/Oasis.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromEth2Dai(
address takerToken,
address makerToken,
uint256[] calldata makerTokenAmounts
)
external
view
returns (uint256[] memory takerTokenAmounts);
} }

View File

@@ -327,7 +327,6 @@ contract TestERC20BridgeSampler is
bytes memory bytes memory
) )
public public
view
returns ( returns (
LibOrder.OrderInfo memory orderInfo, LibOrder.OrderInfo memory orderInfo,
uint256 fillableTakerAssetAmount, uint256 fillableTakerAssetAmount,

View File

@@ -25,19 +25,6 @@ blockchainTests('erc20-bridge-sampler', env => {
const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7'; const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7';
const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab';
const ERC20_PROXY_ID = '0xf47261b0'; const ERC20_PROXY_ID = '0xf47261b0';
const INVALID_ASSET_PROXY_ASSET_DATA = hexUtils.concat('0xf47261b1', hexUtils.leftPad(randomAddress()));
const INVALID_ASSET_DATA = hexUtils.random(37);
const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap'];
const BUY_SOURCES = ['Eth2Dai', 'Uniswap'];
const SOURCE_IDS: { [source: string]: string } = {
Uniswap: '0xc0a47dfe034b400b47bdad5fecda2621de6c4d95',
Eth2Dai: '0x39755357759ce0d7f32dc8dc45414cca409ae24e',
Kyber: '0x818e6fecd516ecc3849daf6845e3ec868087b755',
};
const EMPTY_ORDERS_ERROR = 'ERC20BridgeSampler/EMPTY_ORDERS';
const UNSUPPORTED_ASSET_PROXY_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_ASSET_PROXY';
const INVALID_ASSET_DATA_ERROR = 'ERC20BridgeSampler/INVALID_ASSET_DATA';
const UNSUPPORTED_SOURCE_ERROR = 'ERC20BridgeSampler/UNSUPPORTED_SOURCE';
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR'; const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
const MAKER_TOKEN = randomAddress(); const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress();
@@ -324,329 +311,6 @@ blockchainTests('erc20-bridge-sampler', env => {
}); });
}); });
describe('queryOrdersAndSampleSells()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableTakerAssetAmount);
const [orderInfos] = await testContract
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => SOURCE_IDS[n]), takerTokenAmounts)
.callAsync();
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
const [, quotes] = await testContract
.queryOrdersAndSampleSells(ORDERS, SIGNATURES, SELL_SOURCES.map(n => SOURCE_IDS[n]), sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no orders are passed in', async () => {
const tx = testContract
.queryOrdersAndSampleSells([], [], SELL_SOURCES.map(n => SOURCE_IDS[n]), getSampleAmounts(TAKER_TOKEN))
.callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
});
it('throws with an unsupported source', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
ORDERS,
SIGNATURES,
[...SELL_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()],
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR);
});
it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR);
});
it('throws with invalid maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR);
});
it('throws with invalid taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleSells(
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
SELL_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR);
});
});
describe('queryOrdersAndSampleBuys()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns expected fillable amounts for each order', async () => {
const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedFillableAmounts = ORDERS.map(getDeterministicFillableMakerAssetAmount);
const [orderInfos] = await testContract
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => SOURCE_IDS[n]), takerTokenAmounts)
.callAsync();
expect(orderInfos).to.deep.eq(expectedFillableAmounts);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
const [, quotes] = await testContract
.queryOrdersAndSampleBuys(ORDERS, SIGNATURES, BUY_SOURCES.map(n => SOURCE_IDS[n]), sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws if no orders are passed in', async () => {
const tx = testContract
.queryOrdersAndSampleBuys([], [], BUY_SOURCES.map(n => SOURCE_IDS[n]), getSampleAmounts(MAKER_TOKEN))
.callAsync();
return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR);
});
it('throws with an unsupported source', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS,
SIGNATURES,
[...BUY_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()],
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
it('throws if kyber is passed in as a source', async () => {
const sources = [...BUY_SOURCES, 'Kyber'];
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS,
SIGNATURES,
sources.map(n => SOURCE_IDS[n]),
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
it('throws with non-ERC20 maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR);
});
it('throws with non-ERC20 taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR);
});
it('throws with invalid maker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS.map(o => ({
...o,
makerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR);
});
it('throws with invalid taker asset data', async () => {
const tx = testContract
.queryOrdersAndSampleBuys(
ORDERS.map(o => ({
...o,
takerAssetData: INVALID_ASSET_DATA,
})),
SIGNATURES,
BUY_SOURCES.map(n => SOURCE_IDS[n]),
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR);
});
});
describe('sampleSells()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns empty quotes with no sample amounts', async () => {
const emptyQuotes = _.times(SELL_SOURCES.length, () => []);
const quotes = await testContract
.sampleSells(SELL_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, [])
.callAsync();
expect(quotes).to.deep.eq(emptyQuotes);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts);
const quotes = await testContract
.sampleSells(SELL_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can return quotes for some sources', async () => {
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
const sources = _.sampleSize(SELL_SOURCES, 1);
const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts);
const quotes = await testContract
.sampleSells(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws with an unsupported source', async () => {
const tx = testContract
.sampleSells(
[...SELL_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()],
TAKER_TOKEN,
MAKER_TOKEN,
getSampleAmounts(TAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
});
describe('sampleBuys()', () => {
before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
});
it('returns empty quotes with no sample amounts', async () => {
const emptyQuotes = _.times(BUY_SOURCES.length, () => []);
const quotes = await testContract
.sampleBuys(BUY_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, [])
.callAsync();
expect(quotes).to.deep.eq(emptyQuotes);
});
it('can return quotes for all sources', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts);
const quotes = await testContract
.sampleBuys(BUY_SOURCES.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('can return quotes for some sources', async () => {
const sampleAmounts = getSampleAmounts(MAKER_TOKEN);
const sources = _.sampleSize(BUY_SOURCES, 1);
const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts);
const quotes = await testContract
.sampleBuys(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
.callAsync();
expect(quotes).to.deep.eq(expectedQuotes);
});
it('throws with an unsupported source', async () => {
const tx = testContract
.sampleBuys(
[...BUY_SOURCES.map(n => SOURCE_IDS[n]), randomAddress()],
TAKER_TOKEN,
MAKER_TOKEN,
getSampleAmounts(MAKER_TOKEN),
)
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
it('throws if kyber is passed in as a source', async () => {
const sources = [...BUY_SOURCES, 'Kyber'];
const tx = testContract
.sampleBuys(sources.map(n => SOURCE_IDS[n]), TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(MAKER_TOKEN))
.callAsync();
return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR);
});
});
blockchainTests.resets('sampleSellsFromKyberNetwork()', () => { blockchainTests.resets('sampleSellsFromKyberNetwork()', () => {
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
@@ -1051,4 +715,65 @@ blockchainTests('erc20-bridge-sampler', env => {
expect(quotes).to.deep.eq(expectedQuotes); expect(quotes).to.deep.eq(expectedQuotes);
}); });
}); });
describe('batchCall()', () => {
it('can call one function', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
const calls = [
testContract.getOrderFillableTakerAssetAmounts(orders, signatures).getABIEncodedTransactionData(),
];
const r = await testContract.batchCall(calls).callAsync();
expect(r).to.be.length(1);
const actual = testContract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableTakerAssetAmounts', r[0]);
expect(actual).to.deep.eq(expected);
});
it('can call two functions', async () => {
const numOrders = _.random(1, 10);
const orders = _.times(2, () => createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders));
const signatures: string[] = _.times(numOrders, i => hexUtils.random());
const expecteds = [
orders[0].map(getDeterministicFillableTakerAssetAmount),
orders[1].map(getDeterministicFillableMakerAssetAmount),
];
const calls = [
testContract.getOrderFillableTakerAssetAmounts(orders[0], signatures).getABIEncodedTransactionData(),
testContract.getOrderFillableMakerAssetAmounts(orders[1], signatures).getABIEncodedTransactionData(),
];
const r = await testContract.batchCall(calls).callAsync();
expect(r).to.be.length(2);
expect(testContract.getABIDecodedReturnData('getOrderFillableTakerAssetAmounts', r[0])).to.deep.eq(
expecteds[0],
);
expect(testContract.getABIDecodedReturnData('getOrderFillableMakerAssetAmounts', r[1])).to.deep.eq(
expecteds[1],
);
});
it('can make recursive calls', async () => {
const numOrders = _.random(1, 10);
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders);
const signatures: string[] = _.times(numOrders, i => hexUtils.random());
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
let r = await testContract
.batchCall([
testContract
.batchCall([
testContract
.getOrderFillableTakerAssetAmounts(orders, signatures)
.getABIEncodedTransactionData(),
])
.getABIEncodedTransactionData(),
])
.callAsync();
expect(r).to.be.length(1);
r = testContract.getABIDecodedReturnData<string[]>('batchCall', r[0]);
expect(r).to.be.length(1);
expect(testContract.getABIDecodedReturnData('getOrderFillableTakerAssetAmounts', r[0])).to.deep.eq(
expected,
);
});
});
}); });