Merge pull request #2477 from 0xProject/feat/batched-sampler
ERC20BridgeSampler: batchCall()
This commit is contained in:
commit
dfd9443f74
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Catch reverts to `DevUtils` calls",
|
||||
"pr": 2476
|
||||
},
|
||||
{
|
||||
"note": "Remove wrapper functions and introduce `batchCall()`",
|
||||
"pr": 2477
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -21,7 +21,6 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
@ -36,154 +35,36 @@ contract ERC20BridgeSampler is
|
||||
IERC20BridgeSampler,
|
||||
DeploymentConstants
|
||||
{
|
||||
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
|
||||
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3;
|
||||
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3;
|
||||
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3;
|
||||
uint256 constant internal DEV_UTILS_CALL_GAS = 500e3;
|
||||
address constant private UNISWAP_SOURCE = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95;
|
||||
address constant private ETH2DAI_SOURCE = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e;
|
||||
address constant private KYBER_SOURCE = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755;
|
||||
/// @dev Gas limit for DevUtils calls.
|
||||
uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k
|
||||
/// @dev Gas limit for Kyber calls.
|
||||
uint256 constant internal KYBER_CALL_GAS = 1500e3; // 1.5m
|
||||
/// @dev Gas limit for Uniswap calls.
|
||||
uint256 constant internal UNISWAP_CALL_GAS = 150e3; // 150k
|
||||
/// @dev Base gas limit for Eth2Dai calls.
|
||||
uint256 constant internal ETH2DAI_CALL_GAS = 1000e3; // 1m
|
||||
|
||||
/// @dev Query batches of native orders and sample sell 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 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[][] memory orders,
|
||||
bytes[][] memory orderSignatures,
|
||||
address[] memory sources,
|
||||
uint256[][] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
||||
/// @param callDatas ABI-encoded call data for each function call.
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
OrdersAndSample[] memory ordersAndSamples
|
||||
)
|
||||
returns (bytes[] memory callResults)
|
||||
{
|
||||
ordersAndSamples = new OrdersAndSample[](orders.length);
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
(
|
||||
uint256[] memory orderFillableAssetAmounts,
|
||||
uint256[][] memory tokenAmountsBySource
|
||||
) = queryOrdersAndSampleSells(orders[i], orderSignatures[i], sources, takerTokenAmounts[i]);
|
||||
ordersAndSamples[i].orderFillableAssetAmounts = orderFillableAssetAmounts;
|
||||
ordersAndSamples[i].tokenAmountsBySource = tokenAmountsBySource;
|
||||
callResults = new bytes[](callDatas.length);
|
||||
for (uint256 i = 0; i != callDatas.length; ++i) {
|
||||
(bool didSucceed, bytes memory resultData) = address(this).staticcall(callDatas[i]);
|
||||
if (!didSucceed) {
|
||||
assembly { revert(add(resultData, 0x20), mload(resultData)) }
|
||||
}
|
||||
callResults[i] = resultData;
|
||||
}
|
||||
}
|
||||
|
||||
/// @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.
|
||||
/// 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 orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @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.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
@ -354,7 +175,7 @@ contract ERC20BridgeSampler is
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getKyberNetworkProxyAddress().staticcall.gas(KYBER_SAMPLE_CALL_GAS)(
|
||||
_getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IKyberNetwork(0).getExpectedRate.selector,
|
||||
_takerToken,
|
||||
@ -396,7 +217,7 @@ contract ERC20BridgeSampler is
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)(
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getBuyAmount.selector,
|
||||
makerToken,
|
||||
@ -433,7 +254,7 @@ contract ERC20BridgeSampler is
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_SAMPLE_CALL_GAS)(
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getPayAmount.selector,
|
||||
takerToken,
|
||||
@ -599,7 +420,7 @@ contract ERC20BridgeSampler is
|
||||
}
|
||||
bytes memory resultData;
|
||||
(didSucceed, resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)(
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
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.
|
||||
/// Throws if the exchange does not exist.
|
||||
/// @param tokenAddress Address of the token contract.
|
||||
@ -677,23 +445,9 @@ contract ERC20BridgeSampler is
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Extract the token address from ERC20 proxy asset data.
|
||||
/// @param assetData ERC20 asset data.
|
||||
/// @return tokenAddress The decoded token address.
|
||||
function _assetDataToTokenAddress(bytes memory assetData)
|
||||
private
|
||||
pure
|
||||
returns (address tokenAddress)
|
||||
{
|
||||
require(assetData.length == 36, "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");
|
||||
}
|
||||
|
||||
/// @dev Assert that the tokens in a trade pair are valid.
|
||||
/// @param makerToken Address of the maker token.
|
||||
/// @param takerToken Address of the taker token.
|
||||
function _assertValidPair(address makerToken, address takerToken)
|
||||
private
|
||||
pure
|
||||
|
@ -23,98 +23,14 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IERC20BridgeSampler {
|
||||
struct OrdersAndSample {
|
||||
uint256[] orderFillableAssetAmounts;
|
||||
uint256[][] tokenAmountsBySource;
|
||||
}
|
||||
|
||||
/// @dev Query batches of native orders and sample sell 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 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
|
||||
)
|
||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
||||
/// @param callDatas ABI-encoded call data for each function call.
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
view
|
||||
returns (
|
||||
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
|
||||
);
|
||||
returns (bytes[] memory callResults);
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
@ -142,39 +58,78 @@ interface IERC20BridgeSampler {
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts);
|
||||
|
||||
/// @dev Sample sell quotes on multiple DEXes at once.
|
||||
/// @param sources Address of each DEX. Passing in an unsupported DEX will throw.
|
||||
/// @dev Sample sell quotes from Kyber.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmountsBySource Maker amounts bought for each source at
|
||||
/// each taker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function sampleSells(
|
||||
address[] calldata sources,
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromKyberNetwork(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[][] memory makerTokenAmountsBySource);
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @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.
|
||||
/// @dev Sample sell quotes from Eth2Dai/Oasis.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmountsBySource Taker amounts sold for each source at
|
||||
/// each maker token amount. First indexed by source index, then sample
|
||||
/// index.
|
||||
function sampleBuys(
|
||||
address[] calldata sources,
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromEth2Dai(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] 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 makerToken,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
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);
|
||||
}
|
||||
|
@ -327,7 +327,6 @@ contract TestERC20BridgeSampler is
|
||||
bytes memory
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
|
@ -25,19 +25,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7';
|
||||
const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab';
|
||||
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 MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
@ -190,7 +177,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
}
|
||||
|
||||
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
|
||||
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
|
||||
const hash = getPackedHash(hexUtils.leftPad(order.salt));
|
||||
const orderStatus = new BigNumber(hash).mod(100).toNumber() > 90 ? 5 : 3;
|
||||
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
|
||||
if (orderStatus !== 3 || !isValidSignature) {
|
||||
@ -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()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
@ -1051,4 +715,65 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "4.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Use `batchCall()` version of the `ERC20BridgeSampler` contract",
|
||||
"pr": 2477
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1581204851,
|
||||
"version": "4.1.2",
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
} from './types';
|
||||
import { assert } from './utils/assert';
|
||||
import { calculateLiquidity } from './utils/calculate_liquidity';
|
||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||
import { DexOrderSampler, MarketOperationUtils } from './utils/market_operation_utils';
|
||||
import { dummyOrderUtils } from './utils/market_operation_utils/dummy_order_utils';
|
||||
import { orderPrunerUtils } from './utils/order_prune_utils';
|
||||
import { OrderStateUtils } from './utils/order_state_utils';
|
||||
@ -162,12 +162,12 @@ export class SwapQuoter {
|
||||
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||
this._protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
|
||||
this._orderStateUtils = new OrderStateUtils(this._devUtilsContract);
|
||||
const samplerContract = new IERC20BridgeSamplerContract(
|
||||
this._contractAddresses.erc20BridgeSampler,
|
||||
this.provider,
|
||||
{ gas: samplerGasLimit },
|
||||
const sampler = new DexOrderSampler(
|
||||
new IERC20BridgeSamplerContract(this._contractAddresses.erc20BridgeSampler, this.provider, {
|
||||
gas: samplerGasLimit,
|
||||
}),
|
||||
);
|
||||
this._marketOperationUtils = new MarketOperationUtils(samplerContract, this._contractAddresses, {
|
||||
this._marketOperationUtils = new MarketOperationUtils(sampler, this._contractAddresses, {
|
||||
chainId,
|
||||
exchangeAddress: this._contractAddresses.exchange,
|
||||
});
|
||||
|
@ -4,15 +4,6 @@ import { ERC20BridgeSource, GetMarketOrdersOpts } from './types';
|
||||
|
||||
const INFINITE_TIMESTAMP_SEC = new BigNumber(2524604400);
|
||||
|
||||
/**
|
||||
* Convert a source to a canonical address used by the sampler contract.
|
||||
*/
|
||||
const SOURCE_TO_ADDRESS: { [key: string]: string } = {
|
||||
[ERC20BridgeSource.Eth2Dai]: '0x39755357759ce0d7f32dc8dc45414cca409ae24e',
|
||||
[ERC20BridgeSource.Uniswap]: '0xc0a47dfe034b400b47bdad5fecda2621de6c4d95',
|
||||
[ERC20BridgeSource.Kyber]: '0x818e6fecd516ecc3849daf6845e3ec868087b755',
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid sources for market sell.
|
||||
*/
|
||||
@ -36,7 +27,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
|
||||
export const constants = {
|
||||
INFINITE_TIMESTAMP_SEC,
|
||||
SOURCE_TO_ADDRESS,
|
||||
SELL_SOURCES,
|
||||
BUY_SOURCES,
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
|
||||
import { assetDataUtils, ERC20AssetData, orderCalculationUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
@ -11,7 +10,7 @@ import { fillableAmountsUtils } from '../fillable_amounts_utils';
|
||||
import { constants as marketOperationUtilConstants } from './constants';
|
||||
import { CreateOrderUtils } from './create_order';
|
||||
import { comparePathOutputs, FillsOptimizer, getPathOutput } from './fill_optimizer';
|
||||
import { DexOrderSampler } from './sampler';
|
||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||
import {
|
||||
AggregationError,
|
||||
CollapsedFill,
|
||||
@ -27,22 +26,20 @@ import {
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
export { DexOrderSampler } from './sampler';
|
||||
|
||||
const { ZERO_AMOUNT } = constants;
|
||||
const { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, ERC20_PROXY_ID, SELL_SOURCES } = marketOperationUtilConstants;
|
||||
|
||||
export class MarketOperationUtils {
|
||||
private readonly _dexSampler: DexOrderSampler;
|
||||
private readonly _createOrderUtils: CreateOrderUtils;
|
||||
private readonly _orderDomain: OrderDomain;
|
||||
|
||||
constructor(
|
||||
samplerContract: IERC20BridgeSamplerContract,
|
||||
private readonly _sampler: DexOrderSampler,
|
||||
contractAddresses: ContractAddresses,
|
||||
orderDomain: OrderDomain,
|
||||
private readonly _orderDomain: OrderDomain,
|
||||
) {
|
||||
this._dexSampler = new DexOrderSampler(samplerContract);
|
||||
this._createOrderUtils = new CreateOrderUtils(contractAddresses);
|
||||
this._orderDomain = orderDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,10 +62,15 @@ export class MarketOperationUtils {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
...opts,
|
||||
};
|
||||
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketSellAsync(
|
||||
nativeOrders,
|
||||
DexOrderSampler.getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
difference(SELL_SOURCES, _opts.excludedSources),
|
||||
const [makerToken, takerToken] = getOrderTokens(nativeOrders[0]);
|
||||
const [fillableAmounts, dexQuotes] = await this._sampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(nativeOrders),
|
||||
DexOrderSampler.ops.getSellQuotes(
|
||||
difference(SELL_SOURCES, _opts.excludedSources),
|
||||
makerToken,
|
||||
takerToken,
|
||||
getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
),
|
||||
);
|
||||
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
|
||||
nativeOrders,
|
||||
@ -104,11 +106,10 @@ export class MarketOperationUtils {
|
||||
if (!optimalPath) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
const [outputToken, inputToken] = getOrderTokens(nativeOrders[0]);
|
||||
return this._createOrderUtils.createSellOrdersFromPath(
|
||||
this._orderDomain,
|
||||
inputToken,
|
||||
outputToken,
|
||||
takerToken,
|
||||
makerToken,
|
||||
collapsePath(optimalPath, false),
|
||||
_opts.bridgeSlippage,
|
||||
);
|
||||
@ -134,11 +135,15 @@ export class MarketOperationUtils {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
...opts,
|
||||
};
|
||||
|
||||
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketBuyAsync(
|
||||
nativeOrders,
|
||||
DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
difference(BUY_SOURCES, _opts.excludedSources),
|
||||
const [makerToken, takerToken] = getOrderTokens(nativeOrders[0]);
|
||||
const [fillableAmounts, dexQuotes] = await this._sampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders),
|
||||
DexOrderSampler.ops.getBuyQuotes(
|
||||
difference(BUY_SOURCES, _opts.excludedSources),
|
||||
makerToken,
|
||||
takerToken,
|
||||
getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
),
|
||||
);
|
||||
const signedOrderWithFillableAmounts = this._createBuyOrdersPathFromSamplerResultIfExists(
|
||||
nativeOrders,
|
||||
@ -174,17 +179,25 @@ export class MarketOperationUtils {
|
||||
...opts,
|
||||
};
|
||||
|
||||
const batchSampleResults = await this._dexSampler.getBatchFillableAmountsAndSampleMarketBuyAsync(
|
||||
batchNativeOrders,
|
||||
makerAmounts.map(makerAmount => DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples)),
|
||||
difference(BUY_SOURCES, _opts.excludedSources),
|
||||
);
|
||||
return batchSampleResults.map(([fillableAmounts, dexQuotes], i) =>
|
||||
const sources = difference(BUY_SOURCES, _opts.excludedSources);
|
||||
const ops = [
|
||||
...batchNativeOrders.map(orders => DexOrderSampler.ops.getOrderFillableMakerAmounts(orders)),
|
||||
...batchNativeOrders.map((orders, i) =>
|
||||
DexOrderSampler.ops.getBuyQuotes(sources, getOrderTokens(orders[0])[0], getOrderTokens(orders[0])[1], [
|
||||
makerAmounts[i],
|
||||
]),
|
||||
),
|
||||
];
|
||||
const executeResults = await this._sampler.executeBatchAsync(ops);
|
||||
const batchFillableAmounts = executeResults.slice(0, batchNativeOrders.length) as BigNumber[][];
|
||||
const batchDexQuotes = executeResults.slice(batchNativeOrders.length) as DexSample[][][];
|
||||
|
||||
return batchFillableAmounts.map((fillableAmounts, i) =>
|
||||
this._createBuyOrdersPathFromSamplerResultIfExists(
|
||||
batchNativeOrders[i],
|
||||
makerAmounts[i],
|
||||
fillableAmounts,
|
||||
dexQuotes,
|
||||
batchDexQuotes[i],
|
||||
_opts,
|
||||
),
|
||||
);
|
||||
|
@ -2,99 +2,330 @@ import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { constants as marketOperationUtilConstants } from './constants';
|
||||
import { DexSample, ERC20BridgeSource } from './types';
|
||||
|
||||
const { SOURCE_TO_ADDRESS } = marketOperationUtilConstants;
|
||||
/**
|
||||
* A composable operation the be run in `DexOrderSampler.executeAsync()`.
|
||||
*/
|
||||
export interface BatchedOperation<TResult> {
|
||||
encodeCall(contract: IERC20BridgeSamplerContract): string;
|
||||
handleCallResultsAsync(contract: IERC20BridgeSamplerContract, callResults: string): Promise<TResult>;
|
||||
}
|
||||
|
||||
export class DexOrderSampler {
|
||||
private readonly _samplerContract: IERC20BridgeSamplerContract;
|
||||
|
||||
/**
|
||||
* Generate sample amounts up to `maxFillAmount`.
|
||||
*/
|
||||
public static getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
|
||||
const distribution = [...Array<BigNumber>(numSamples)].map((v, i) => new BigNumber(expBase).pow(i));
|
||||
const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
|
||||
const amounts = stepSizes.map((s, i) => {
|
||||
return maxFillAmount
|
||||
.times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)]))
|
||||
.integerValue(BigNumber.ROUND_UP);
|
||||
/**
|
||||
* Composable operations that can be batched in a single transaction,
|
||||
* for use with `DexOrderSampler.executeAsync()`.
|
||||
*/
|
||||
const samplerOperations = {
|
||||
getOrderFillableTakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.getOrderFillableTakerAssetAmounts(orders, orders.map(o => o.signature))
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableTakerAssetAmounts', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getOrderFillableMakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.getOrderFillableMakerAssetAmounts(orders, orders.map(o => o.signature))
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableMakerAssetAmounts', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getKyberSellQuotes(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.sampleSellsFromKyberNetwork(takerToken, makerToken, takerFillAmounts)
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromKyberNetwork', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getUniswapSellQuotes(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.sampleSellsFromUniswap(takerToken, makerToken, takerFillAmounts)
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromUniswap', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getEth2DaiSellQuotes(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.sampleSellsFromEth2Dai(takerToken, makerToken, takerFillAmounts)
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromEth2Dai', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getUniswapBuyQuotes(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.sampleBuysFromUniswap(takerToken, makerToken, makerFillAmounts)
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromUniswap', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getEth2DaiBuyQuotes(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<BigNumber[]> {
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
return contract
|
||||
.sampleBuysFromEth2Dai(takerToken, makerToken, makerFillAmounts)
|
||||
.getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromEth2Dai', callResults);
|
||||
},
|
||||
};
|
||||
},
|
||||
getSellQuotes(
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<DexSample[][]> {
|
||||
const subOps = sources.map(source => {
|
||||
if (source === ERC20BridgeSource.Eth2Dai) {
|
||||
return samplerOperations.getEth2DaiSellQuotes(makerToken, takerToken, takerFillAmounts);
|
||||
} else if (source === ERC20BridgeSource.Uniswap) {
|
||||
return samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts);
|
||||
} else if (source === ERC20BridgeSource.Kyber) {
|
||||
return samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts);
|
||||
} else {
|
||||
throw new Error(`Unsupported sell sample source: ${source}`);
|
||||
}
|
||||
});
|
||||
return amounts;
|
||||
}
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
const subCalls = subOps.map(op => op.encodeCall(contract));
|
||||
return contract.batchCall(subCalls).getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
|
||||
const samples = await Promise.all(
|
||||
subOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])),
|
||||
);
|
||||
return sources.map((source, i) => {
|
||||
return samples[i].map((output, j) => ({
|
||||
source,
|
||||
output,
|
||||
input: takerFillAmounts[j],
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
getBuyQuotes(
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
): BatchedOperation<DexSample[][]> {
|
||||
const subOps = sources.map(source => {
|
||||
if (source === ERC20BridgeSource.Eth2Dai) {
|
||||
return samplerOperations.getEth2DaiBuyQuotes(makerToken, takerToken, makerFillAmounts);
|
||||
} else if (source === ERC20BridgeSource.Uniswap) {
|
||||
return samplerOperations.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts);
|
||||
} else {
|
||||
throw new Error(`Unsupported buy sample source: ${source}`);
|
||||
}
|
||||
});
|
||||
return {
|
||||
encodeCall: contract => {
|
||||
const subCalls = subOps.map(op => op.encodeCall(contract));
|
||||
return contract.batchCall(subCalls).getABIEncodedTransactionData();
|
||||
},
|
||||
handleCallResultsAsync: async (contract, callResults) => {
|
||||
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
|
||||
const samples = await Promise.all(
|
||||
subOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])),
|
||||
);
|
||||
return sources.map((source, i) => {
|
||||
return samples[i].map((output, j) => ({
|
||||
source,
|
||||
output,
|
||||
input: makerFillAmounts[j],
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate sample amounts up to `maxFillAmount`.
|
||||
*/
|
||||
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
|
||||
const distribution = [...Array<BigNumber>(numSamples)].map((v, i) => new BigNumber(expBase).pow(i));
|
||||
const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
|
||||
const amounts = stepSizes.map((s, i) => {
|
||||
return maxFillAmount
|
||||
.times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)]))
|
||||
.integerValue(BigNumber.ROUND_UP);
|
||||
});
|
||||
return amounts;
|
||||
}
|
||||
|
||||
type BatchedOperationResult<T> = T extends BatchedOperation<infer TResult> ? TResult : never;
|
||||
|
||||
/**
|
||||
* Encapsulates interactions with the `ERC20BridgeSampler` contract.
|
||||
*/
|
||||
export class DexOrderSampler {
|
||||
/**
|
||||
* Composable operations that can be batched in a single transaction,
|
||||
* for use with `DexOrderSampler.executeAsync()`.
|
||||
*/
|
||||
public static ops = samplerOperations;
|
||||
private readonly _samplerContract: IERC20BridgeSamplerContract;
|
||||
|
||||
constructor(samplerContract: IERC20BridgeSamplerContract) {
|
||||
this._samplerContract = samplerContract;
|
||||
}
|
||||
|
||||
public async getFillableAmountsAndSampleMarketBuyAsync(
|
||||
nativeOrders: SignedOrder[],
|
||||
sampleAmounts: BigNumber[],
|
||||
sources: ERC20BridgeSource[],
|
||||
): Promise<[BigNumber[], DexSample[][]]> {
|
||||
const signatures = nativeOrders.map(o => o.signature);
|
||||
const [fillableAmount, rawSamples] = await this._samplerContract
|
||||
.queryOrdersAndSampleBuys(nativeOrders, signatures, sources.map(s => SOURCE_TO_ADDRESS[s]), sampleAmounts)
|
||||
.callAsync();
|
||||
const quotes = rawSamples.map((rawDexSamples, sourceIdx) => {
|
||||
const source = sources[sourceIdx];
|
||||
return rawDexSamples.map((sample, sampleIdx) => ({
|
||||
source,
|
||||
input: sampleAmounts[sampleIdx],
|
||||
output: sample,
|
||||
}));
|
||||
});
|
||||
return [fillableAmount, quotes];
|
||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1
|
||||
>(...ops: [T1]): Promise<[
|
||||
BatchedOperationResult<T1>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2
|
||||
>(...ops: [T1, T2]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3
|
||||
>(...ops: [T1, T2, T3]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4
|
||||
>(...ops: [T1, T2, T3, T4]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5
|
||||
>(...ops: [T1, T2, T3, T4, T5]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5, T6
|
||||
>(...ops: [T1, T2, T3, T4, T5, T6]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>,
|
||||
BatchedOperationResult<T6>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5, T6, T7
|
||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>,
|
||||
BatchedOperationResult<T6>,
|
||||
BatchedOperationResult<T7>
|
||||
]>;
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1, T2, T3, T4, T5, T6, T7, T8
|
||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8]): Promise<[
|
||||
BatchedOperationResult<T1>,
|
||||
BatchedOperationResult<T2>,
|
||||
BatchedOperationResult<T3>,
|
||||
BatchedOperationResult<T4>,
|
||||
BatchedOperationResult<T5>,
|
||||
BatchedOperationResult<T6>,
|
||||
BatchedOperationResult<T7>,
|
||||
BatchedOperationResult<T8>
|
||||
]>;
|
||||
|
||||
/**
|
||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
||||
*/
|
||||
public async executeAsync(...ops: any[]): Promise<any[]> {
|
||||
return this.executeBatchAsync(ops);
|
||||
}
|
||||
|
||||
public async getBatchFillableAmountsAndSampleMarketBuyAsync(
|
||||
nativeOrders: SignedOrder[][],
|
||||
sampleAmounts: BigNumber[][],
|
||||
sources: ERC20BridgeSource[],
|
||||
): Promise<Array<[BigNumber[], DexSample[][]]>> {
|
||||
const signatures = nativeOrders.map(o => o.map(i => i.signature));
|
||||
const fillableAmountsAndSamples = await this._samplerContract
|
||||
.queryBatchOrdersAndSampleBuys(
|
||||
nativeOrders,
|
||||
signatures,
|
||||
sources.map(s => SOURCE_TO_ADDRESS[s]),
|
||||
sampleAmounts,
|
||||
)
|
||||
.callAsync();
|
||||
const batchFillableAmountsAndQuotes: Array<[BigNumber[], DexSample[][]]> = [];
|
||||
fillableAmountsAndSamples.forEach((sampleResult, i) => {
|
||||
const { tokenAmountsBySource, orderFillableAssetAmounts } = sampleResult;
|
||||
const quotes = tokenAmountsBySource.map((rawDexSamples, sourceIdx) => {
|
||||
const source = sources[sourceIdx];
|
||||
return rawDexSamples.map((sample, sampleIdx) => ({
|
||||
source,
|
||||
input: sampleAmounts[i][sampleIdx],
|
||||
output: sample,
|
||||
}));
|
||||
});
|
||||
batchFillableAmountsAndQuotes.push([orderFillableAssetAmounts, quotes]);
|
||||
});
|
||||
return batchFillableAmountsAndQuotes;
|
||||
}
|
||||
|
||||
public async getFillableAmountsAndSampleMarketSellAsync(
|
||||
nativeOrders: SignedOrder[],
|
||||
sampleAmounts: BigNumber[],
|
||||
sources: ERC20BridgeSource[],
|
||||
): Promise<[BigNumber[], DexSample[][]]> {
|
||||
const signatures = nativeOrders.map(o => o.signature);
|
||||
const [fillableAmount, rawSamples] = await this._samplerContract
|
||||
.queryOrdersAndSampleSells(nativeOrders, signatures, sources.map(s => SOURCE_TO_ADDRESS[s]), sampleAmounts)
|
||||
.callAsync();
|
||||
const quotes = rawSamples.map((rawDexSamples, sourceIdx) => {
|
||||
const source = sources[sourceIdx];
|
||||
return rawDexSamples.map((sample, sampleIdx) => ({
|
||||
source,
|
||||
input: sampleAmounts[sampleIdx],
|
||||
output: sample,
|
||||
}));
|
||||
});
|
||||
return [fillableAmount, quotes];
|
||||
/**
|
||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
||||
* Takes an arbitrary length array, but is not typesafe.
|
||||
*/
|
||||
public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(ops: T): Promise<any[]> {
|
||||
const callDatas = ops.map(o => o.encodeCall(this._samplerContract));
|
||||
const callResults = await this._samplerContract.batchCall(callDatas).callAsync();
|
||||
return Promise.all(callResults.map(async (r, i) => ops[i].handleCallResultsAsync(this._samplerContract, r)));
|
||||
}
|
||||
}
|
||||
|
357
packages/asset-swapper/test/dex_sampler_test.ts
Normal file
357
packages/asset-swapper/test/dex_sampler_test.ts
Normal file
@ -0,0 +1,357 @@
|
||||
import { constants, expect, getRandomFloat, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
|
||||
import { ERC20BridgeSource } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
import { MockSamplerContract } from './utils/mock_sampler_contract';
|
||||
|
||||
const CHAIN_ID = 1;
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
describe('DexSampler tests', () => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
|
||||
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
||||
|
||||
describe('getSampleAmounts()', () => {
|
||||
const FILL_AMOUNT = getRandomInteger(1, 1e18);
|
||||
const NUM_SAMPLES = 16;
|
||||
|
||||
it('generates the correct number of amounts', () => {
|
||||
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts).to.be.length(NUM_SAMPLES);
|
||||
});
|
||||
|
||||
it('first amount is nonzero', () => {
|
||||
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts[0]).to.not.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('last amount is the fill amount', () => {
|
||||
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts[NUM_SAMPLES - 1]).to.bignumber.eq(FILL_AMOUNT);
|
||||
});
|
||||
|
||||
it('can generate a single amount', () => {
|
||||
const amounts = getSampleAmounts(FILL_AMOUNT, 1);
|
||||
expect(amounts).to.be.length(1);
|
||||
expect(amounts[0]).to.bignumber.eq(FILL_AMOUNT);
|
||||
});
|
||||
|
||||
it('generates ascending amounts', () => {
|
||||
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
for (const i of _.times(NUM_SAMPLES).slice(1)) {
|
||||
const prev = amounts[i - 1];
|
||||
const amount = amounts[i];
|
||||
expect(prev).to.bignumber.lt(amount);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createOrder(overrides?: Partial<SignedOrder>): SignedOrder {
|
||||
return {
|
||||
chainId: CHAIN_ID,
|
||||
exchangeAddress: randomAddress(),
|
||||
makerAddress: constants.NULL_ADDRESS,
|
||||
takerAddress: constants.NULL_ADDRESS,
|
||||
senderAddress: constants.NULL_ADDRESS,
|
||||
feeRecipientAddress: randomAddress(),
|
||||
salt: generatePseudoRandomSalt(),
|
||||
expirationTimeSeconds: getRandomInteger(0, 2 ** 64),
|
||||
makerAssetData: MAKER_ASSET_DATA,
|
||||
takerAssetData: TAKER_ASSET_DATA,
|
||||
makerFeeAssetData: constants.NULL_BYTES,
|
||||
takerFeeAssetData: constants.NULL_BYTES,
|
||||
makerAssetAmount: getRandomInteger(1, 1e18),
|
||||
takerAssetAmount: getRandomInteger(1, 1e18),
|
||||
makerFee: constants.ZERO_AMOUNT,
|
||||
takerFee: constants.ZERO_AMOUNT,
|
||||
signature: hexUtils.random(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
const ORDERS = _.times(4, () => createOrder());
|
||||
const SIMPLE_ORDERS = ORDERS.map(o => _.omit(o, ['signature', 'chainId', 'exchangeAddress']));
|
||||
|
||||
describe('operations', () => {
|
||||
it('getOrderFillableMakerAmounts()', async () => {
|
||||
const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||
const sampler = new MockSamplerContract({
|
||||
getOrderFillableMakerAssetAmounts: (orders, signatures) => {
|
||||
expect(orders).to.deep.eq(SIMPLE_ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
return expectedFillableAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(ORDERS),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('getOrderFillableTakerAmounts()', async () => {
|
||||
const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||
const sampler = new MockSamplerContract({
|
||||
getOrderFillableTakerAssetAmounts: (orders, signatures) => {
|
||||
expect(orders).to.deep.eq(SIMPLE_ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
return expectedFillableAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(ORDERS),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
|
||||
it('getKyberSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromKyberNetwork: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getKyberSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getEth2DaiSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getEth2DaiSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromUniswap: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getEth2DaiBuyQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleBuysFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return expectedTakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getEth2DaiBuyQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapBuyQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleBuysFromUniswap: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return expectedTakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapBuyQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
interface RatesBySource {
|
||||
[src: string]: BigNumber;
|
||||
}
|
||||
|
||||
it('getSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const sources = [ERC20BridgeSource.Kyber, ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap];
|
||||
const ratesBySource: RatesBySource = {
|
||||
[ERC20BridgeSource.Kyber]: getRandomFloat(0, 100),
|
||||
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
|
||||
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
|
||||
};
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromKyberNetwork: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Kyber]).integerValue());
|
||||
},
|
||||
sampleSellsFromUniswap: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
|
||||
},
|
||||
sampleSellsFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getSellQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(quotes).to.be.length(sources.length);
|
||||
const expectedQuotes = sources.map(s =>
|
||||
expectedTakerFillAmounts.map(a => ({
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[s]).integerValue(),
|
||||
})),
|
||||
);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('getBuyQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap];
|
||||
const ratesBySource: RatesBySource = {
|
||||
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
|
||||
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
|
||||
};
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleBuysFromUniswap: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
|
||||
},
|
||||
sampleBuysFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getBuyQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(quotes).to.be.length(sources.length);
|
||||
const expectedQuotes = sources.map(s =>
|
||||
expectedMakerFillAmounts.map(a => ({
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.times(ratesBySource[s]).integerValue(),
|
||||
})),
|
||||
);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batched operations', () => {
|
||||
it('getOrderFillableMakerAmounts(), getOrderFillableTakerAmounts()', async () => {
|
||||
const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||
const expectedFillableMakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||
const sampler = new MockSamplerContract({
|
||||
getOrderFillableMakerAssetAmounts: (orders, signatures) => {
|
||||
expect(orders).to.deep.eq(SIMPLE_ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
return expectedFillableMakerAmounts;
|
||||
},
|
||||
getOrderFillableTakerAssetAmounts: (orders, signatures) => {
|
||||
expect(orders).to.deep.eq(SIMPLE_ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
return expectedFillableTakerAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(ORDERS),
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(ORDERS),
|
||||
);
|
||||
expect(fillableMakerAmounts).to.deep.eq(expectedFillableMakerAmounts);
|
||||
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable-next-line: max-file-line-count
|
@ -10,23 +10,20 @@ import {
|
||||
} from '@0x/contracts-test-utils';
|
||||
|
||||
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { Order, SignedOrder } from '@0x/types';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import { constants as marketOperationUtilConstants } from '../src/utils/market_operation_utils/constants';
|
||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||
import { ERC20BridgeSource } from '../src/utils/market_operation_utils/types';
|
||||
import { DexSample, ERC20BridgeSource } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
import { MockSamplerContract, QueryAndSampleResult } from './utils/mock_sampler_contract';
|
||||
const { BUY_SOURCES, SELL_SOURCES } = marketOperationUtilConstants;
|
||||
|
||||
const { SOURCE_TO_ADDRESS, BUY_SOURCES, SELL_SOURCES } = marketOperationUtilConstants;
|
||||
|
||||
// Because the bridges and the DEX sources are only deployed on mainnet, tests will resort to using mainnet addresses
|
||||
const CHAIN_ID = 1;
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
describe('MarketOperationUtils tests', () => {
|
||||
const CHAIN_ID = 1;
|
||||
const contractAddresses = getContractAddressesForChainOrThrow(CHAIN_ID);
|
||||
const ETH2DAI_BRIDGE_ADDRESS = contractAddresses.eth2DaiBridge;
|
||||
const KYBER_BRIDGE_ADDRESS = contractAddresses.kyberBridge;
|
||||
@ -36,10 +33,15 @@ describe('MarketOperationUtils tests', () => {
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
|
||||
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
||||
let originalSamplerOperations: any;
|
||||
|
||||
interface RatesBySource {
|
||||
[source: string]: Numberish[];
|
||||
}
|
||||
before(() => {
|
||||
originalSamplerOperations = DexOrderSampler.ops;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
DexOrderSampler.ops = originalSamplerOperations;
|
||||
});
|
||||
|
||||
function createOrder(overrides?: Partial<SignedOrder>): SignedOrder {
|
||||
return {
|
||||
@ -82,15 +84,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
throw new Error(`Unknown bridge address: ${bridgeAddress}`);
|
||||
}
|
||||
|
||||
function getSourceFromAddress(sourceAddress: string): ERC20BridgeSource {
|
||||
for (const k of Object.keys(SOURCE_TO_ADDRESS)) {
|
||||
if (SOURCE_TO_ADDRESS[k].toLowerCase() === sourceAddress.toLowerCase()) {
|
||||
return k as ERC20BridgeSource;
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown source address: ${sourceAddress}`);
|
||||
}
|
||||
|
||||
function assertSamePrefix(actual: string, expected: string): void {
|
||||
expect(actual.substr(0, expected.length)).to.eq(expected);
|
||||
}
|
||||
@ -107,7 +100,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
function createOrdersFromBuyRates(makerAssetAmount: BigNumber, rates: Numberish[]): SignedOrder[] {
|
||||
const singleMakerAssetAmount = makerAssetAmount.div(rates.length).integerValue(BigNumber.ROUND_UP);
|
||||
return (rates as any).map((r: Numberish) =>
|
||||
return rates.map(r =>
|
||||
createOrder({
|
||||
makerAssetAmount: singleMakerAssetAmount,
|
||||
takerAssetAmount: singleMakerAssetAmount.div(r).integerValue(),
|
||||
@ -115,272 +108,197 @@ describe('MarketOperationUtils tests', () => {
|
||||
);
|
||||
}
|
||||
|
||||
function createSamplerFromSellRates(rates: RatesBySource): MockSamplerContract {
|
||||
return new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
const fillableTakerAssetAmounts = orders.map(o => o.takerAssetAmount);
|
||||
const samplesBySourceIndex = sources.map(s =>
|
||||
fillAmounts.map((fillAmount, idx) =>
|
||||
fillAmount.times(rates[getSourceFromAddress(s)][idx]).integerValue(BigNumber.ROUND_UP),
|
||||
),
|
||||
);
|
||||
return [fillableTakerAssetAmounts, samplesBySourceIndex];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createSamplerFromBuyRates(rates: RatesBySource): MockSamplerContract {
|
||||
return new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
const fillableMakerAssetAmounts = orders.map(o => o.makerAssetAmount);
|
||||
const samplesBySourceIndex = sources.map(s =>
|
||||
fillAmounts.map((fillAmount, idx) =>
|
||||
fillAmount.div(rates[getSourceFromAddress(s)][idx]).integerValue(BigNumber.ROUND_UP),
|
||||
),
|
||||
);
|
||||
return [fillableMakerAssetAmounts, samplesBySourceIndex];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const DUMMY_QUERY_AND_SAMPLE_HANDLER_SELL = (
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
sources: string[],
|
||||
fillAmounts: BigNumber[],
|
||||
): QueryAndSampleResult => [
|
||||
orders.map((order: Order) => order.takerAssetAmount),
|
||||
sources.map(() => fillAmounts.map(() => getRandomInteger(1, 1e18))),
|
||||
];
|
||||
|
||||
const DUMMY_QUERY_AND_SAMPLE_HANDLER_BUY = (
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
sources: string[],
|
||||
fillAmounts: BigNumber[],
|
||||
): QueryAndSampleResult => [
|
||||
orders.map((order: Order) => order.makerAssetAmount),
|
||||
sources.map(() => fillAmounts.map(() => getRandomInteger(1, 1e18))),
|
||||
];
|
||||
|
||||
const ORDER_DOMAIN = {
|
||||
exchangeAddress: contractAddresses.exchange,
|
||||
chainId: CHAIN_ID,
|
||||
};
|
||||
|
||||
describe('DexOrderSampler', () => {
|
||||
describe('getSampleAmounts()', () => {
|
||||
const FILL_AMOUNT = getRandomInteger(1, 1e18);
|
||||
const NUM_SAMPLES = 16;
|
||||
type GetQuotesOperation = (makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => BigNumber[];
|
||||
|
||||
it('generates the correct number of amounts', () => {
|
||||
const amounts = DexOrderSampler.getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts).to.be.length(NUM_SAMPLES);
|
||||
});
|
||||
|
||||
it('first amount is nonzero', () => {
|
||||
const amounts = DexOrderSampler.getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts[0]).to.not.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('last amount is the fill amount', () => {
|
||||
const amounts = DexOrderSampler.getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
expect(amounts[NUM_SAMPLES - 1]).to.bignumber.eq(FILL_AMOUNT);
|
||||
});
|
||||
|
||||
it('can generate a single amount', () => {
|
||||
const amounts = DexOrderSampler.getSampleAmounts(FILL_AMOUNT, 1);
|
||||
expect(amounts).to.be.length(1);
|
||||
expect(amounts[0]).to.bignumber.eq(FILL_AMOUNT);
|
||||
});
|
||||
|
||||
it('generates ascending amounts', () => {
|
||||
const amounts = DexOrderSampler.getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
|
||||
for (const i of _.times(NUM_SAMPLES).slice(1)) {
|
||||
const prev = amounts[i - 1];
|
||||
const amount = amounts[i];
|
||||
expect(prev).to.bignumber.lt(amount);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFillableAmountsAndSampleMarketOperationAsync()', () => {
|
||||
const SAMPLE_AMOUNTS = [100, 500, 1000].map(v => new BigNumber(v));
|
||||
const ORDERS = _.times(4, () => createOrder());
|
||||
|
||||
it('makes an eth_call with the correct arguments for a sell', async () => {
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
expect(orders).to.deep.eq(ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
expect(sources).to.deep.eq(SELL_SOURCES.map(s => SOURCE_TO_ADDRESS[s]));
|
||||
expect(fillAmounts).to.deep.eq(SAMPLE_AMOUNTS);
|
||||
return [
|
||||
orders.map(() => getRandomInteger(1, 1e18)),
|
||||
sources.map(() => fillAmounts.map(() => getRandomInteger(1, 1e18))),
|
||||
];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
await dexOrderSampler.getFillableAmountsAndSampleMarketSellAsync(ORDERS, SAMPLE_AMOUNTS, SELL_SOURCES);
|
||||
});
|
||||
|
||||
it('makes an eth_call with the correct arguments for a buy', async () => {
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
expect(orders).to.deep.eq(ORDERS);
|
||||
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
|
||||
expect(sources).to.deep.eq(BUY_SOURCES.map(s => SOURCE_TO_ADDRESS[s]));
|
||||
expect(fillAmounts).to.deep.eq(SAMPLE_AMOUNTS);
|
||||
return [
|
||||
orders.map(() => getRandomInteger(1, 1e18)),
|
||||
sources.map(() => fillAmounts.map(() => getRandomInteger(1, 1e18))),
|
||||
];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
await dexOrderSampler.getFillableAmountsAndSampleMarketBuyAsync(ORDERS, SAMPLE_AMOUNTS, BUY_SOURCES);
|
||||
});
|
||||
|
||||
it('returns correct fillable amounts', async () => {
|
||||
const fillableAmounts = _.times(SAMPLE_AMOUNTS.length, () => getRandomInteger(1, 1e18));
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => [
|
||||
fillableAmounts,
|
||||
sources.map(() => fillAmounts.map(() => getRandomInteger(1, 1e18))),
|
||||
],
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [actualFillableAmounts] = await dexOrderSampler.getFillableAmountsAndSampleMarketSellAsync(
|
||||
ORDERS,
|
||||
SAMPLE_AMOUNTS,
|
||||
SELL_SOURCES,
|
||||
);
|
||||
expect(actualFillableAmounts).to.deep.eq(fillableAmounts);
|
||||
});
|
||||
|
||||
it('converts samples to DEX quotes', async () => {
|
||||
const quotes = SELL_SOURCES.map(source =>
|
||||
SAMPLE_AMOUNTS.map(s => ({
|
||||
source,
|
||||
input: s,
|
||||
output: getRandomInteger(1, 1e18),
|
||||
})),
|
||||
);
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => [
|
||||
orders.map(() => getRandomInteger(1, 1e18)),
|
||||
quotes.map(q => q.map(s => s.output)),
|
||||
],
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [, actualQuotes] = await dexOrderSampler.getFillableAmountsAndSampleMarketSellAsync(
|
||||
ORDERS,
|
||||
SAMPLE_AMOUNTS,
|
||||
SELL_SOURCES,
|
||||
);
|
||||
expect(actualQuotes).to.deep.eq(quotes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createRandomRates(numSamples: number = 32): RatesBySource {
|
||||
const ALL_SOURCES = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Kyber,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
];
|
||||
return _.zipObject(
|
||||
ALL_SOURCES,
|
||||
_.times(ALL_SOURCES.length, () => _.fill(new Array(numSamples), getRandomFloat(1e-3, 2))),
|
||||
);
|
||||
function createGetSellQuotesOperationFromRates(rates: Numberish[]): GetQuotesOperation {
|
||||
return (makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return fillAmounts.map((a, i) => a.times(rates[i]).integerValue());
|
||||
};
|
||||
}
|
||||
|
||||
function createGetBuyQuotesOperationFromRates(rates: Numberish[]): GetQuotesOperation {
|
||||
return (makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return fillAmounts.map((a, i) => a.div(rates[i]).integerValue());
|
||||
};
|
||||
}
|
||||
|
||||
type GetMultipleQuotesOperation = (
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
) => DexSample[][];
|
||||
|
||||
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return sources.map(s =>
|
||||
fillAmounts.map((a, i) => ({
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.times(rates[s][i]).integerValue(),
|
||||
})),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return sources.map(s =>
|
||||
fillAmounts.map((a, i) => ({
|
||||
source: s,
|
||||
input: a,
|
||||
output: a.div(rates[s][i]).integerValue(),
|
||||
})),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createDecreasingRates(count: number): BigNumber[] {
|
||||
const rates: BigNumber[] = [];
|
||||
const initialRate = getRandomFloat(1e-3, 1e2);
|
||||
_.times(count, () => getRandomFloat(0.95, 1)).forEach((r, i) => {
|
||||
const prevRate = i === 0 ? initialRate : rates[i - 1];
|
||||
rates.push(prevRate.times(r));
|
||||
});
|
||||
return rates;
|
||||
}
|
||||
|
||||
const NUM_SAMPLES = 3;
|
||||
|
||||
interface RatesBySource {
|
||||
[source: string]: Numberish[];
|
||||
}
|
||||
|
||||
const DEFAULT_RATES: RatesBySource = {
|
||||
[ERC20BridgeSource.Native]: createDecreasingRates(NUM_SAMPLES),
|
||||
[ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES),
|
||||
[ERC20BridgeSource.Kyber]: createDecreasingRates(NUM_SAMPLES),
|
||||
[ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES),
|
||||
};
|
||||
|
||||
function findSourceWithMaxOutput(rates: RatesBySource): ERC20BridgeSource {
|
||||
const minSourceRates = Object.keys(rates).map(s => _.last(rates[s]) as BigNumber);
|
||||
const bestSourceRate = BigNumber.max(...minSourceRates);
|
||||
let source = Object.keys(rates)[_.findIndex(minSourceRates, t => bestSourceRate.eq(t))] as ERC20BridgeSource;
|
||||
// Native order rates play by different rules.
|
||||
if (source !== ERC20BridgeSource.Native) {
|
||||
const nativeTotalRate = BigNumber.sum(...rates[ERC20BridgeSource.Native]).div(
|
||||
rates[ERC20BridgeSource.Native].length,
|
||||
);
|
||||
if (nativeTotalRate.gt(bestSourceRate)) {
|
||||
source = ERC20BridgeSource.Native;
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
const DEFAULT_OPS = {
|
||||
getOrderFillableTakerAmounts(orders: SignedOrder[]): BigNumber[] {
|
||||
return orders.map(o => o.takerAssetAmount);
|
||||
},
|
||||
getOrderFillableMakerAmounts(orders: SignedOrder[]): BigNumber[] {
|
||||
return orders.map(o => o.makerAssetAmount);
|
||||
},
|
||||
getKyberSellQuotes: createGetSellQuotesOperationFromRates(DEFAULT_RATES[ERC20BridgeSource.Kyber]),
|
||||
getUniswapSellQuotes: createGetSellQuotesOperationFromRates(DEFAULT_RATES[ERC20BridgeSource.Uniswap]),
|
||||
getEth2DaiSellQuotes: createGetSellQuotesOperationFromRates(DEFAULT_RATES[ERC20BridgeSource.Eth2Dai]),
|
||||
getUniswapBuyQuotes: createGetBuyQuotesOperationFromRates(DEFAULT_RATES[ERC20BridgeSource.Uniswap]),
|
||||
getEth2DaiBuyQuotes: createGetBuyQuotesOperationFromRates(DEFAULT_RATES[ERC20BridgeSource.Eth2Dai]),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||
};
|
||||
|
||||
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
|
||||
DexOrderSampler.ops = {
|
||||
...DEFAULT_OPS,
|
||||
...ops,
|
||||
} as any;
|
||||
}
|
||||
|
||||
const MOCK_SAMPLER = ({
|
||||
async executeAsync(...ops: any[]): Promise<any[]> {
|
||||
return ops;
|
||||
},
|
||||
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
||||
return ops;
|
||||
},
|
||||
} as any) as DexOrderSampler;
|
||||
|
||||
describe('MarketOperationUtils', () => {
|
||||
let marketOperationUtils: MarketOperationUtils;
|
||||
|
||||
before(async () => {
|
||||
marketOperationUtils = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||
});
|
||||
|
||||
describe('getMarketSellOrdersAsync()', () => {
|
||||
const FILL_AMOUNT = getRandomInteger(1, 1e18);
|
||||
const SOURCE_RATES = createRandomRates();
|
||||
const ORDERS = createOrdersFromSellRates(
|
||||
FILL_AMOUNT,
|
||||
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_SAMPLER = createSamplerFromSellRates(SOURCE_RATES);
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0, sampleDistributionBase: 1 };
|
||||
const defaultMarketOperationUtils = new MarketOperationUtils(
|
||||
DEFAULT_SAMPLER,
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
_.times(NUM_SAMPLES, i => DEFAULT_RATES[ERC20BridgeSource.Native][i]),
|
||||
);
|
||||
const DEFAULT_OPTS = { numSamples: NUM_SAMPLES, runLimit: 0, sampleDistributionBase: 1 };
|
||||
|
||||
it('calls `getFillableAmountsAndSampleMarketSellAsync()`', async () => {
|
||||
let wasCalled = false;
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (...args) => {
|
||||
wasCalled = true;
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_SELL(...args);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, DEFAULT_OPTS);
|
||||
expect(wasCalled).to.be.true();
|
||||
beforeEach(() => {
|
||||
replaceSamplerOps();
|
||||
});
|
||||
|
||||
it('queries `numSamples` samples', async () => {
|
||||
const numSamples = _.random(1, 16);
|
||||
let fillAmountsLength = 0;
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
fillAmountsLength = fillAmounts.length;
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_SELL(orders, signatures, sources, fillAmounts);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
numSamples,
|
||||
});
|
||||
expect(fillAmountsLength).eq(numSamples);
|
||||
expect(actualNumSamples).eq(numSamples);
|
||||
});
|
||||
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
sourcesPolled = sources.map(a => getSourceFromAddress(a));
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_SELL(orders, signatures, sources, fillAmounts);
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
excludedSources: [],
|
||||
});
|
||||
expect(sourcesPolled).to.deep.eq(SELL_SOURCES);
|
||||
expect(sourcesPolled.sort()).to.deep.eq(SELL_SOURCES.slice().sort());
|
||||
});
|
||||
|
||||
it('does not poll DEXes in `excludedSources`', async () => {
|
||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
sourcesPolled = sources.map(a => getSourceFromAddress(a));
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_SELL(orders, signatures, sources, fillAmounts);
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
excludedSources,
|
||||
});
|
||||
expect(sourcesPolled).to.deep.eq(_.without(SELL_SOURCES, ...excludedSources));
|
||||
expect(sourcesPolled.sort()).to.deep.eq(_.without(SELL_SOURCES, ...excludedSources).sort());
|
||||
});
|
||||
|
||||
it('returns the most cost-effective single source if `runLimit == 0`', async () => {
|
||||
const bestRate = BigNumber.max(..._.flatten(Object.values(SOURCE_RATES)));
|
||||
const bestSource = _.findKey(SOURCE_RATES, ([r]) => new BigNumber(r).eq(bestRate));
|
||||
const bestSource = findSourceWithMaxOutput(DEFAULT_RATES);
|
||||
expect(bestSource).to.exist('');
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
runLimit: 0,
|
||||
});
|
||||
@ -390,7 +308,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
});
|
||||
|
||||
it('generates bridge orders with correct asset data', async () => {
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketSellOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -414,7 +332,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
});
|
||||
|
||||
it('generates bridge orders with correct taker amount', async () => {
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketSellOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -426,7 +344,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
||||
const bridgeSlippage = _.random(0.1, true);
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketSellOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -435,7 +353,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
expect(improvedOrders).to.not.be.length(0);
|
||||
for (const order of improvedOrders) {
|
||||
const source = getSourceFromAssetData(order.makerAssetData);
|
||||
const expectedMakerAmount = FILL_AMOUNT.times(SOURCE_RATES[source][0]);
|
||||
const expectedMakerAmount = FILL_AMOUNT.times(_.last(DEFAULT_RATES[source]) as BigNumber);
|
||||
const slippage = 1 - order.makerAssetAmount.div(expectedMakerAmount.plus(1)).toNumber();
|
||||
assertRoughlyEquals(slippage, bridgeSlippage, 8);
|
||||
}
|
||||
@ -450,7 +368,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
makerAssetAmount: dustAmount.times(maxRate.plus(0.01)),
|
||||
takerAssetAmount: dustAmount,
|
||||
});
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketSellOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
_.shuffle([dustOrder, ...ORDERS]),
|
||||
FILL_AMOUNT,
|
||||
// Ignore all DEX sources so only native orders are returned.
|
||||
@ -468,11 +386,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Kyber] = [0.7, 0.05, 0.05, 0.05];
|
||||
const marketOperationUtils = new MarketOperationUtils(
|
||||
createSamplerFromSellRates(rates),
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
@ -495,11 +411,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Kyber] = [0.4, 0.05, 0.05, 0.05];
|
||||
const marketOperationUtils = new MarketOperationUtils(
|
||||
createSamplerFromSellRates(rates),
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
@ -522,11 +436,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.15, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.15, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Kyber] = [0.7, 0.05, 0.05, 0.05];
|
||||
const marketOperationUtils = new MarketOperationUtils(
|
||||
createSamplerFromSellRates(rates),
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
@ -546,61 +458,40 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
describe('getMarketBuyOrdersAsync()', () => {
|
||||
const FILL_AMOUNT = getRandomInteger(1, 1e18);
|
||||
const SOURCE_RATES = _.omit(createRandomRates(), [ERC20BridgeSource.Kyber]);
|
||||
const ORDERS = createOrdersFromBuyRates(
|
||||
FILL_AMOUNT,
|
||||
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_SAMPLER = createSamplerFromBuyRates(SOURCE_RATES);
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0, sampleDistributionBase: 1 };
|
||||
const defaultMarketOperationUtils = new MarketOperationUtils(
|
||||
DEFAULT_SAMPLER,
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
_.times(NUM_SAMPLES, () => DEFAULT_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_OPTS = { numSamples: NUM_SAMPLES, runLimit: 0, sampleDistributionBase: 1 };
|
||||
|
||||
it('calls `getFillableAmountsAndSampleMarketSellAsync()`', async () => {
|
||||
let wasCalled = false;
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (...args) => {
|
||||
wasCalled = true;
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_BUY(...args);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, DEFAULT_OPTS);
|
||||
expect(wasCalled).to.be.true();
|
||||
beforeEach(() => {
|
||||
replaceSamplerOps();
|
||||
});
|
||||
|
||||
it('queries `numSamples` samples', async () => {
|
||||
const numSamples = _.random(1, 16);
|
||||
let fillAmountsLength = 0;
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
fillAmountsLength = fillAmounts.length;
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_BUY(orders, signatures, sources, fillAmounts);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
numSamples,
|
||||
});
|
||||
expect(fillAmountsLength).eq(numSamples);
|
||||
expect(actualNumSamples).eq(numSamples);
|
||||
});
|
||||
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
sourcesPolled = sources.map(a => getSourceFromAddress(a));
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_BUY(orders, signatures, sources, fillAmounts);
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
excludedSources: [],
|
||||
@ -609,16 +500,14 @@ describe('MarketOperationUtils tests', () => {
|
||||
});
|
||||
|
||||
it('does not poll DEXes in `excludedSources`', async () => {
|
||||
const excludedSources = _.sampleSize(BUY_SOURCES, _.random(1, BUY_SOURCES.length));
|
||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
sourcesPolled = sources.map(a => getSourceFromAddress(a));
|
||||
return DUMMY_QUERY_AND_SAMPLE_HANDLER_BUY(orders, signatures, sources, fillAmounts);
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
},
|
||||
});
|
||||
const marketOperationUtils = new MarketOperationUtils(sampler, contractAddresses, ORDER_DOMAIN);
|
||||
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
excludedSources,
|
||||
@ -627,10 +516,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
});
|
||||
|
||||
it('returns the most cost-effective single source if `runLimit == 0`', async () => {
|
||||
const bestRate = BigNumber.max(..._.flatten(Object.values(SOURCE_RATES)));
|
||||
const bestSource = _.findKey(SOURCE_RATES, ([r]) => new BigNumber(r).eq(bestRate));
|
||||
const bestSource = findSourceWithMaxOutput(_.omit(DEFAULT_RATES, ERC20BridgeSource.Kyber));
|
||||
expect(bestSource).to.exist('');
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
runLimit: 0,
|
||||
});
|
||||
@ -638,8 +526,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
expect(uniqueAssetDatas).to.be.length(1);
|
||||
expect(getSourceFromAssetData(uniqueAssetDatas[0])).to.be.eq(bestSource);
|
||||
});
|
||||
|
||||
it('generates bridge orders with correct asset data', async () => {
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketBuyOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -663,7 +552,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
});
|
||||
|
||||
it('generates bridge orders with correct taker amount', async () => {
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketBuyOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -675,7 +564,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
it('generates bridge orders with max slippage of `bridgeSlippage`', async () => {
|
||||
const bridgeSlippage = _.random(0.1, true);
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketBuyOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
// Pass in empty orders to prevent native orders from being used.
|
||||
ORDERS.map(o => ({ ...o, makerAssetAmount: constants.ZERO_AMOUNT })),
|
||||
FILL_AMOUNT,
|
||||
@ -684,9 +573,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
expect(improvedOrders).to.not.be.length(0);
|
||||
for (const order of improvedOrders) {
|
||||
const source = getSourceFromAssetData(order.makerAssetData);
|
||||
const expectedTakerAmount = FILL_AMOUNT.div(SOURCE_RATES[source][0]).integerValue(
|
||||
BigNumber.ROUND_UP,
|
||||
);
|
||||
const expectedTakerAmount = FILL_AMOUNT.div(_.last(DEFAULT_RATES[source]) as BigNumber);
|
||||
const slippage = order.takerAssetAmount.div(expectedTakerAmount.plus(1)).toNumber() - 1;
|
||||
assertRoughlyEquals(slippage, bridgeSlippage, 8);
|
||||
}
|
||||
@ -701,7 +588,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
makerAssetAmount: dustAmount,
|
||||
takerAssetAmount: dustAmount.div(maxRate.plus(0.01)).integerValue(BigNumber.ROUND_DOWN),
|
||||
});
|
||||
const improvedOrders = await defaultMarketOperationUtils.getMarketBuyOrdersAsync(
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
_.shuffle([dustOrder, ...ORDERS]),
|
||||
FILL_AMOUNT,
|
||||
// Ignore all DEX sources so only native orders are returned.
|
||||
@ -718,11 +605,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1];
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
const marketOperationUtils = new MarketOperationUtils(
|
||||
createSamplerFromBuyRates(rates),
|
||||
contractAddresses,
|
||||
ORDER_DOMAIN,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
FILL_AMOUNT,
|
||||
|
@ -8,7 +8,7 @@ import 'mocha';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import { CalculateSwapQuoteOpts, SignedOrderWithFillableAmounts } from '../src/types';
|
||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import { DexOrderSampler, MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import { constants as marketOperationUtilConstants } from '../src/utils/market_operation_utils/constants';
|
||||
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
|
||||
import { SwapQuoteCalculator } from '../src/utils/swap_quote_calculator';
|
||||
@ -43,27 +43,26 @@ const CALCULATE_SWAP_QUOTE_OPTS: CalculateSwapQuoteOpts = {
|
||||
},
|
||||
};
|
||||
|
||||
const createSamplerFromSignedOrdersWithFillableAmounts = (
|
||||
function createSamplerFromSignedOrdersWithFillableAmounts(
|
||||
signedOrders: SignedOrderWithFillableAmounts[],
|
||||
): MockSamplerContract => {
|
||||
const sampler = new MockSamplerContract({
|
||||
queryOrdersAndSampleBuys: (orders, signatures, sources, fillAmounts) => {
|
||||
const fillableAmounts = signatures.map((s: string) => {
|
||||
const order = (signedOrders.find(o => o.signature === s) as any) as SignedOrderWithFillableAmounts;
|
||||
return order.fillableMakerAssetAmount;
|
||||
});
|
||||
return [fillableAmounts, sources.map(() => fillAmounts.map(() => constants.ZERO_AMOUNT))];
|
||||
},
|
||||
queryOrdersAndSampleSells: (orders, signatures, sources, fillAmounts) => {
|
||||
const fillableAmounts = signatures.map((s: string) => {
|
||||
const order = (signedOrders.find(o => o.signature === s) as any) as SignedOrderWithFillableAmounts;
|
||||
return order.fillableTakerAssetAmount;
|
||||
});
|
||||
return [fillableAmounts, sources.map(() => fillAmounts.map(() => constants.ZERO_AMOUNT))];
|
||||
},
|
||||
});
|
||||
return sampler;
|
||||
};
|
||||
): DexOrderSampler {
|
||||
const sampleDexHandler = (takerToken: string, makerToken: string, amounts: BigNumber[]) => {
|
||||
return amounts.map(() => constants.ZERO_AMOUNT);
|
||||
};
|
||||
return new DexOrderSampler(
|
||||
new MockSamplerContract({
|
||||
getOrderFillableMakerAssetAmounts: (orders, signatures) =>
|
||||
orders.map((o, i) => signedOrders[i].fillableMakerAssetAmount),
|
||||
getOrderFillableTakerAssetAmounts: (orders, signatures) =>
|
||||
orders.map((o, i) => signedOrders[i].fillableTakerAssetAmount),
|
||||
sampleSellsFromEth2Dai: sampleDexHandler,
|
||||
sampleSellsFromKyberNetwork: sampleDexHandler,
|
||||
sampleSellsFromUniswap: sampleDexHandler,
|
||||
sampleBuysFromEth2Dai: sampleDexHandler,
|
||||
sampleBuysFromUniswap: sampleDexHandler,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(dorothy-zbornak): Replace these tests entirely with unit tests because
|
||||
// omg they're a nightmare to maintain.
|
||||
|
@ -2,15 +2,25 @@ import { ContractFunctionObj } from '@0x/base-contract';
|
||||
import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
|
||||
import { constants } from '@0x/contracts-test-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
|
||||
export type QueryAndSampleResult = [BigNumber[], BigNumber[][]];
|
||||
export type QueryAndSampleHandler = (
|
||||
export type GetOrderFillableAssetAmountResult = BigNumber[];
|
||||
export type GetOrderFillableAssetAmountHandler = (
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
sources: string[],
|
||||
fillAmounts: BigNumber[],
|
||||
) => QueryAndSampleResult;
|
||||
) => GetOrderFillableAssetAmountResult;
|
||||
|
||||
export type SampleResults = BigNumber[];
|
||||
export type SampleSellsHandler = (
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
export type SampleBuysHandler = (
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
|
||||
const DUMMY_PROVIDER = {
|
||||
sendAsync: (...args: any[]): any => {
|
||||
@ -18,56 +28,156 @@ const DUMMY_PROVIDER = {
|
||||
},
|
||||
};
|
||||
|
||||
interface Handlers {
|
||||
getOrderFillableMakerAssetAmounts: GetOrderFillableAssetAmountHandler;
|
||||
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
|
||||
sampleSellsFromKyberNetwork: SampleSellsHandler;
|
||||
sampleSellsFromEth2Dai: SampleSellsHandler;
|
||||
sampleSellsFromUniswap: SampleSellsHandler;
|
||||
sampleBuysFromEth2Dai: SampleBuysHandler;
|
||||
sampleBuysFromUniswap: SampleBuysHandler;
|
||||
}
|
||||
|
||||
export class MockSamplerContract extends IERC20BridgeSamplerContract {
|
||||
public readonly queryOrdersAndSampleSellsHandler?: QueryAndSampleHandler;
|
||||
public readonly queryOrdersAndSampleBuysHandler?: QueryAndSampleHandler;
|
||||
private readonly _handlers: Partial<Handlers> = {};
|
||||
|
||||
public constructor(
|
||||
handlers?: Partial<{
|
||||
queryOrdersAndSampleSells: QueryAndSampleHandler;
|
||||
queryOrdersAndSampleBuys: QueryAndSampleHandler;
|
||||
}>,
|
||||
) {
|
||||
public constructor(handlers: Partial<Handlers> = {}) {
|
||||
super(constants.NULL_ADDRESS, DUMMY_PROVIDER);
|
||||
const _handlers = {
|
||||
queryOrdersAndSampleSells: undefined,
|
||||
queryOrdersAndSampleBuys: undefined,
|
||||
...handlers,
|
||||
};
|
||||
this.queryOrdersAndSampleSellsHandler = _handlers.queryOrdersAndSampleSells;
|
||||
this.queryOrdersAndSampleBuysHandler = _handlers.queryOrdersAndSampleBuys;
|
||||
this._handlers = handlers;
|
||||
}
|
||||
|
||||
public queryOrdersAndSampleSells(
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
sources: string[],
|
||||
fillAmounts: BigNumber[],
|
||||
): ContractFunctionObj<QueryAndSampleResult> {
|
||||
public batchCall(callDatas: string[]): ContractFunctionObj<string[]> {
|
||||
return {
|
||||
...super.queryOrdersAndSampleSells(orders, signatures, sources, fillAmounts),
|
||||
callAsync: async (...args: any[]): Promise<QueryAndSampleResult> => {
|
||||
if (!this.queryOrdersAndSampleSellsHandler) {
|
||||
throw new Error('queryOrdersAndSampleSells handler undefined');
|
||||
}
|
||||
return this.queryOrdersAndSampleSellsHandler(orders, signatures, sources, fillAmounts);
|
||||
},
|
||||
...super.batchCall(callDatas),
|
||||
callAsync: async (...callArgs: any[]) => callDatas.map(callData => this._callEncodedFunction(callData)),
|
||||
};
|
||||
}
|
||||
|
||||
public queryOrdersAndSampleBuys(
|
||||
public getOrderFillableMakerAssetAmounts(
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
sources: string[],
|
||||
fillAmounts: BigNumber[],
|
||||
): ContractFunctionObj<QueryAndSampleResult> {
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.getOrderFillableMakerAssetAmounts,
|
||||
this._handlers.getOrderFillableMakerAssetAmounts,
|
||||
orders,
|
||||
signatures,
|
||||
);
|
||||
}
|
||||
|
||||
public getOrderFillableTakerAssetAmounts(
|
||||
orders: Order[],
|
||||
signatures: string[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.getOrderFillableTakerAssetAmounts,
|
||||
this._handlers.getOrderFillableTakerAssetAmounts,
|
||||
orders,
|
||||
signatures,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromKyberNetwork(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromKyberNetwork,
|
||||
this._handlers.sampleSellsFromKyberNetwork,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromEth2Dai(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromEth2Dai,
|
||||
this._handlers.sampleSellsFromEth2Dai,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromUniswap(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswap,
|
||||
this._handlers.sampleSellsFromUniswap,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleBuysFromEth2Dai(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromEth2Dai,
|
||||
this._handlers.sampleBuysFromEth2Dai,
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleBuysFromUniswap(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswap,
|
||||
this._handlers.sampleBuysFromUniswap,
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
private _callEncodedFunction(callData: string): string {
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
const selector = hexUtils.slice(callData, 0, 4);
|
||||
for (const [name, handler] of Object.entries(this._handlers)) {
|
||||
if (handler && this.getSelector(name) === selector) {
|
||||
const args = this.getABIDecodedTransactionData<any>(name, callData);
|
||||
const result = (handler as any)(...args);
|
||||
return this._lookupAbiEncoder(this.getFunctionSignature(name)).encodeReturnValues([result]);
|
||||
}
|
||||
}
|
||||
if (selector === this.getSelector('batchCall')) {
|
||||
const calls = this.getABIDecodedTransactionData<string[]>('batchCall', callData);
|
||||
const results = calls.map(cd => this._callEncodedFunction(cd));
|
||||
return this._lookupAbiEncoder(this.getFunctionSignature('batchCall')).encodeReturnValues([results]);
|
||||
}
|
||||
throw new Error(`Unkown selector: ${selector}`);
|
||||
}
|
||||
|
||||
private _wrapCall<TArgs extends any[], TResult>(
|
||||
superFn: (this: MockSamplerContract, ...args: TArgs) => ContractFunctionObj<TResult>,
|
||||
handler?: (this: MockSamplerContract, ...args: TArgs) => TResult,
|
||||
// tslint:disable-next-line: trailing-comma
|
||||
...args: TArgs
|
||||
): ContractFunctionObj<TResult> {
|
||||
return {
|
||||
...super.queryOrdersAndSampleBuys(orders, signatures, sources, fillAmounts),
|
||||
callAsync: async (...args: any[]): Promise<QueryAndSampleResult> => {
|
||||
if (!this.queryOrdersAndSampleBuysHandler) {
|
||||
throw new Error('queryOrdersAndSampleBuys handler undefined');
|
||||
...superFn.call(this, ...args),
|
||||
callAsync: async (...callArgs: any[]): Promise<TResult> => {
|
||||
if (!handler) {
|
||||
throw new Error(`${superFn.name} handler undefined`);
|
||||
}
|
||||
return this.queryOrdersAndSampleBuysHandler(orders, signatures, sources, fillAmounts);
|
||||
return handler.call(this, ...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -21,6 +21,10 @@
|
||||
{
|
||||
"note": "Updated Forwarder addresses",
|
||||
"pr": 2469
|
||||
},
|
||||
{
|
||||
"note": "Update `ERC20BridgeSampler` address on mainnet and kovan.",
|
||||
"pr": 2477
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -20,7 +20,7 @@
|
||||
"devUtils": "0x161793cdca4ff9e766a706c2c49c36ac1340bbcd",
|
||||
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
|
||||
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
|
||||
"erc20BridgeSampler": "0x38b55fb7b13cbfbf8781e0f11a77b6199ae10a11",
|
||||
"erc20BridgeSampler": "0x774c53ee7604af93cd3ed1cd25a788a9e0c06fb2",
|
||||
"kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62",
|
||||
"eth2DaiBridge": "0xe3379a1956f4a79f39eb2e87bb441419e167538e",
|
||||
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",
|
||||
@ -108,7 +108,7 @@
|
||||
"erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64",
|
||||
"uniswapBridge": "0x8224aa8fe5c9f07d5a59c735386ff6cc6aaeb568",
|
||||
"eth2DaiBridge": "0x9485d65c6a2fae0d519cced5bd830e57c41998a9",
|
||||
"erc20BridgeSampler": "0x76a3d21fc9c16afd29eb12a5bdcedd5ddbf24357",
|
||||
"erc20BridgeSampler": "0xca6485a7d0f1a42192072dff7518324513294adf",
|
||||
"kyberBridge": "0xde7b2747624a647600fdb349184d0448ab954929",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000",
|
||||
|
@ -22,6 +22,10 @@
|
||||
{
|
||||
"note": "Remove `LibTransactionDecoder` artifact",
|
||||
"pr": 2464
|
||||
},
|
||||
{
|
||||
"note": "Update `IERC20BridgeSampler` artifact",
|
||||
"pr": 2477
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
File diff suppressed because one or more lines are too long
@ -3,6 +3,15 @@
|
||||
"contractName": "IERC20BridgeSampler",
|
||||
"compilerOutput": {
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{ "internalType": "bytes[]", "name": "callDatas", "type": "bytes[]" }],
|
||||
"name": "batchCall",
|
||||
"outputs": [{ "internalType": "bytes[]", "name": "callResults", "type": "bytes[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
@ -74,177 +83,25 @@
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[][]",
|
||||
"name": "orders",
|
||||
"type": "tuple[][]"
|
||||
},
|
||||
{ "internalType": "bytes[][]", "name": "orderSignatures", "type": "bytes[][]" },
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "uint256[][]", "name": "makerTokenAmounts", "type": "uint256[][]" }
|
||||
],
|
||||
"name": "queryBatchOrdersAndSampleBuys",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableAssetAmounts", "type": "uint256[]" },
|
||||
{ "internalType": "uint256[][]", "name": "tokenAmountsBySource", "type": "uint256[][]" }
|
||||
],
|
||||
"internalType": "struct IERC20BridgeSampler.OrdersAndSample[]",
|
||||
"name": "ordersAndSamples",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[][]",
|
||||
"name": "orders",
|
||||
"type": "tuple[][]"
|
||||
},
|
||||
{ "internalType": "bytes[][]", "name": "orderSignatures", "type": "bytes[][]" },
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "uint256[][]", "name": "takerTokenAmounts", "type": "uint256[][]" }
|
||||
],
|
||||
"name": "queryBatchOrdersAndSampleSells",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableAssetAmounts", "type": "uint256[]" },
|
||||
{ "internalType": "uint256[][]", "name": "tokenAmountsBySource", "type": "uint256[][]" }
|
||||
],
|
||||
"internalType": "struct IERC20BridgeSampler.OrdersAndSample[]",
|
||||
"name": "ordersAndSamples",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[]",
|
||||
"name": "orders",
|
||||
"type": "tuple[]"
|
||||
},
|
||||
{ "internalType": "bytes[]", "name": "orderSignatures", "type": "bytes[]" },
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "queryOrdersAndSampleBuys",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableMakerAssetAmounts", "type": "uint256[]" },
|
||||
{ "internalType": "uint256[][]", "name": "makerTokenAmountsBySource", "type": "uint256[][]" }
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[]",
|
||||
"name": "orders",
|
||||
"type": "tuple[]"
|
||||
},
|
||||
{ "internalType": "bytes[]", "name": "orderSignatures", "type": "bytes[]" },
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "queryOrdersAndSampleSells",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableTakerAssetAmounts", "type": "uint256[]" },
|
||||
{ "internalType": "uint256[][]", "name": "makerTokenAmountsBySource", "type": "uint256[][]" }
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuys",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[][]", "name": "takerTokenAmountsBySource", "type": "uint256[][]" }
|
||||
],
|
||||
"name": "sampleBuysFromEth2Dai",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuysFromUniswap",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -252,15 +109,38 @@
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSells",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[][]", "name": "makerTokenAmountsBySource", "type": "uint256[][]" }
|
||||
"name": "sampleSellsFromEth2Dai",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromKyberNetwork",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromUniswap",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
@ -268,6 +148,11 @@
|
||||
],
|
||||
"devdoc": {
|
||||
"methods": {
|
||||
"batchCall(bytes[])": {
|
||||
"details": "Call multiple public functions on this contract in a single transaction.",
|
||||
"params": { "callDatas": "ABI-encoded call data for each function call." },
|
||||
"return": "callResults ABI-encoded results data for each call."
|
||||
},
|
||||
"getOrderFillableMakerAssetAmounts((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[])": {
|
||||
"details": "Queries the fillable maker asset amounts of native orders.",
|
||||
"params": {
|
||||
@ -284,65 +169,50 @@
|
||||
},
|
||||
"return": "orderFillableTakerAssetAmounts How much taker asset can be filled by each order in `orders`."
|
||||
},
|
||||
"queryBatchOrdersAndSampleBuys((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[][])": {
|
||||
"details": "Query batches of native orders and sample buy quotes on multiple DEXes at once.",
|
||||
"params": {
|
||||
"makerTokenAmounts": "Batches of Maker token sell amount for each sample.",
|
||||
"orderSignatures": "Batches of Signatures for each respective order in `orders`.",
|
||||
"orders": "Batches of Native orders to query.",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw."
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"queryBatchOrdersAndSampleSells((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[][])": {
|
||||
"details": "Query batches of native orders and sample sell quotes on multiple DEXes at once.",
|
||||
"params": {
|
||||
"orderSignatures": "Batches of Signatures for each respective order in `orders`.",
|
||||
"orders": "Batches of Native orders to query.",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw.",
|
||||
"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."
|
||||
},
|
||||
"queryOrdersAndSampleBuys((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],address[],uint256[])": {
|
||||
"details": "Query native orders and sample buy quotes on multiple DEXes at once.",
|
||||
"params": {
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"orderSignatures": "Signatures for each respective order in `orders`.",
|
||||
"orders": "Native orders to query.",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw."
|
||||
},
|
||||
"return": "orderFillableMakerAssetAmounts How much maker asset can be filled by each order in `orders`.takerTokenAmountsBySource Taker amounts sold for each source at each maker token amount. First indexed by source index, then sample index."
|
||||
},
|
||||
"queryOrdersAndSampleSells((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],address[],uint256[])": {
|
||||
"details": "Query native orders and sample sell quotes on multiple DEXes at once.",
|
||||
"params": {
|
||||
"orderSignatures": "Signatures for each respective order in `orders`.",
|
||||
"orders": "Native orders to query.",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw.",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "orderFillableTakerAssetAmounts How much taker asset can be filled by each order in `orders`.makerTokenAmountsBySource Maker amounts bought for each source at each taker token amount. First indexed by source index, then sample index."
|
||||
},
|
||||
"sampleBuys(address[],address,address,uint256[])": {
|
||||
"details": "Query native orders and sample buy quotes on multiple DEXes at once.",
|
||||
"sampleBuysFromEth2Dai(address,address,uint256[])": {
|
||||
"details": "Sample buy quotes from Eth2Dai/Oasis.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw.",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Maker token sell amount for each sample."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromUniswap(address,address,uint256[])": {
|
||||
"details": "Sample buy quotes from Uniswap.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token sell amount for each sample.",
|
||||
"takerToken": "Address of the taker token (what to sell)."
|
||||
},
|
||||
"return": "takerTokenAmountsBySource Taker amounts sold for each source at each maker token amount. First indexed by source index, then sample index."
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleSells(address[],address,address,uint256[])": {
|
||||
"details": "Sample sell quotes on multiple DEXes at once.",
|
||||
"sampleSellsFromEth2Dai(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Eth2Dai/Oasis.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"sources": "Address of each DEX. Passing in an unsupported DEX will throw.",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"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."
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromKyberNetwork(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Kyber.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromUniswap(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Uniswap.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -22,6 +22,10 @@
|
||||
{
|
||||
"note": "Remove `LibTransactionDecoder`",
|
||||
"pr": 2464
|
||||
},
|
||||
{
|
||||
"note": "Update `IERC20BridgeSampler` wrapper",
|
||||
"pr": 2477
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user