Merge pull request #2427 from 0xProject/feature/erc20-bridge-sampler/query-multiple-buys-sells
ERC20BridgeSampler: Allow for batching multiple buy/sell samples
This commit is contained in:
commit
4e46bf4697
@ -1,4 +1,17 @@
|
||||
[
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add batch functions to query quotes",
|
||||
"pr": 2427
|
||||
},
|
||||
{
|
||||
"note": "Early exit if a DEX sample fails",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"changes": [
|
||||
|
@ -37,9 +37,73 @@ contract ERC20BridgeSampler is
|
||||
DeploymentConstants
|
||||
{
|
||||
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
|
||||
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 600e3;
|
||||
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3;
|
||||
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3;
|
||||
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 250e3;
|
||||
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3;
|
||||
|
||||
/// @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
|
||||
view
|
||||
returns (
|
||||
OrdersAndSample[] memory ordersAndSamples
|
||||
)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// @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.
|
||||
@ -281,6 +345,8 @@ contract ERC20BridgeSampler is
|
||||
uint256 rate = 0;
|
||||
if (didSucceed) {
|
||||
rate = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] =
|
||||
rate *
|
||||
@ -321,6 +387,8 @@ contract ERC20BridgeSampler is
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else{
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
@ -356,6 +424,8 @@ contract ERC20BridgeSampler is
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
@ -384,26 +454,28 @@ contract ERC20BridgeSampler is
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == _getWethAddress()) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethBought = _callUniswapExchangePriceFunction(
|
||||
uint256 ethBought;
|
||||
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
if (ethBought != 0) {
|
||||
makerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
ethBought
|
||||
@ -412,6 +484,9 @@ contract ERC20BridgeSampler is
|
||||
makerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
if (!didSucceed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,26 +513,28 @@ contract ERC20BridgeSampler is
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == _getWethAddress()) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethSold = _callUniswapExchangePriceFunction(
|
||||
uint256 ethSold;
|
||||
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
if (ethSold != 0) {
|
||||
takerTokenAmounts[i] = _callUniswapExchangePriceFunction(
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
ethSold
|
||||
@ -466,6 +543,9 @@ contract ERC20BridgeSampler is
|
||||
takerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
if (!didSucceed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,12 +573,13 @@ contract ERC20BridgeSampler is
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount)
|
||||
returns (uint256 outputAmount, bool didSucceed)
|
||||
{
|
||||
if (uniswapExchangeAddress == address(0)) {
|
||||
return 0;
|
||||
return (outputAmount, didSucceed);
|
||||
}
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
bytes memory resultData;
|
||||
(didSucceed, resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
|
@ -23,6 +23,52 @@ 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
|
||||
)
|
||||
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.
|
||||
|
@ -338,7 +338,11 @@ contract TestERC20BridgeSampler is
|
||||
bytes32 orderHash = keccak256(abi.encode(order.salt));
|
||||
// Everything else is derived from the hash.
|
||||
orderInfo.orderHash = orderHash;
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus(uint256(orderHash) % MAX_ORDER_STATUS);
|
||||
if (uint256(orderHash) % 100 > 90) {
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.FULLY_FILLED;
|
||||
} else {
|
||||
orderInfo.orderStatus = LibOrder.OrderStatus.FILLABLE;
|
||||
}
|
||||
orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount;
|
||||
fillableTakerAssetAmount =
|
||||
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;
|
||||
|
@ -195,7 +195,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
|
||||
const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
|
||||
const orderStatus = new BigNumber(hash).mod(255).toNumber();
|
||||
const orderStatus = new BigNumber(hash).mod(100).toNumber() > 90 ? 5 : 3;
|
||||
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
|
||||
if (orderStatus !== 3 || !isValidSignature) {
|
||||
return constants.ZERO_AMOUNT;
|
||||
@ -208,7 +208,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
return order.makerAssetAmount
|
||||
.times(takerAmount)
|
||||
.div(order.takerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
.integerValue(BigNumber.ROUND_UP);
|
||||
}
|
||||
|
||||
function getERC20AssetData(tokenAddress: string): string {
|
||||
@ -255,7 +255,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
describe('getOrderFillableTakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const expected = orders.map(getDeterministicFillableTakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
@ -269,7 +269,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
@ -277,7 +277,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
@ -293,7 +293,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
describe('getOrderFillableMakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const expected = orders.map(getDeterministicFillableMakerAssetAmount);
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq(expected);
|
||||
@ -307,7 +307,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('returns zero for an order with zero maker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
@ -315,7 +315,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
it('returns zero for an order with zero taker asset amount', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
|
||||
orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
const signatures: string[] = _.times(orders.length, hexUtils.random);
|
||||
const signatures: string[] = _.times(orders.length, i => hexUtils.random());
|
||||
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
|
||||
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
|
||||
});
|
||||
@ -330,7 +330,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
describe('queryOrdersAndSampleSells()', () => {
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
@ -436,7 +436,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
describe('queryOrdersAndSampleBuys()', () => {
|
||||
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random);
|
||||
const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
|
||||
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
|
@ -277,10 +277,16 @@ for (const abiFileName of abiFileNames) {
|
||||
// use command-line tool black to reformat, if its available
|
||||
try {
|
||||
execSync(`black --line-length 79 ${outFilePath}`);
|
||||
} catch {
|
||||
logUtils.warn(
|
||||
'Failed to reformat generated Python with black. Do you have it installed? Proceeding anyways...',
|
||||
);
|
||||
} catch (e) {
|
||||
const BLACK_RC_CANNOT_PARSE = 123; // empirical black exit code
|
||||
if (e.status === BLACK_RC_CANNOT_PARSE) {
|
||||
logUtils.warn(
|
||||
'Failed to reformat generated Python with black. Exception thrown by execSync("black ...") follows.',
|
||||
);
|
||||
throw e;
|
||||
} else {
|
||||
logUtils.warn('Failed to invoke black. Do you have it installed? Proceeding anyways...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ export const utils = {
|
||||
return tuple.internalType
|
||||
.replace('struct ', '')
|
||||
.replace('.', '')
|
||||
.replace('[]', '');
|
||||
.replace(/\[\]/g, '');
|
||||
} else {
|
||||
const tupleComponents = tuple.components;
|
||||
const lengthOfHashSuffix = 8;
|
||||
|
File diff suppressed because one or more lines are too long
@ -138,6 +138,8 @@ contract AbiGenDummy
|
||||
}
|
||||
|
||||
function methodReturningArrayOfStructs() public pure returns(Struct[] memory) {}
|
||||
function methodAcceptingArrayOfStructs(Struct[] memory) public pure {}
|
||||
function methodAcceptingArrayOfArrayOfStructs(Struct[][] memory) public pure {}
|
||||
|
||||
struct NestedStruct {
|
||||
Struct innerStruct;
|
||||
|
File diff suppressed because one or more lines are too long
@ -90,7 +90,7 @@ class LibDummy:
|
||||
try:
|
||||
for middleware in MIDDLEWARE:
|
||||
web3.middleware_onion.inject(
|
||||
middleware["function"], layer=middleware["layer"]
|
||||
middleware["function"], layer=middleware["layer"],
|
||||
)
|
||||
except ValueError as value_error:
|
||||
if value_error.args == (
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,6 +9,18 @@
|
||||
{
|
||||
"note": "Remove `getSmartContractParamsOrThrow()` from `SwapQuoteConsumer`s.",
|
||||
"pr": 2432
|
||||
},
|
||||
{
|
||||
"note": "Added `getBatchMarketBuySwapQuoteForAssetDataAsync` on `SwapQuoter`",
|
||||
"pr": 2427
|
||||
},
|
||||
{
|
||||
"note": "Add exponential sampling distribution and `sampleDistributionBase` option to `SwapQuoter`",
|
||||
"pr": 2427
|
||||
},
|
||||
{
|
||||
"note": "Compute more accurate best quote price",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -54,6 +54,11 @@ export {
|
||||
SwapQuoteConsumerError,
|
||||
SignedOrderWithFillableAmounts,
|
||||
} from './types';
|
||||
export { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export {
|
||||
ERC20BridgeSource,
|
||||
CollapsedFill,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
} from './utils/market_operation_utils/types';
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
|
@ -226,6 +226,58 @@ export class SwapQuoter {
|
||||
options,
|
||||
)) as MarketBuySwapQuote;
|
||||
}
|
||||
|
||||
public async getBatchMarketBuySwapQuoteForAssetDataAsync(
|
||||
makerAssetDatas: string[],
|
||||
takerAssetData: string,
|
||||
makerAssetBuyAmount: BigNumber[],
|
||||
options: Partial<SwapQuoteRequestOpts> = {},
|
||||
): Promise<Array<MarketBuySwapQuote | undefined>> {
|
||||
makerAssetBuyAmount.map((a, i) => assert.isBigNumber(`makerAssetBuyAmount[${i}]`, a));
|
||||
let gasPrice: BigNumber;
|
||||
const { slippagePercentage, ...calculateSwapQuoteOpts } = _.merge(
|
||||
{},
|
||||
constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||
options,
|
||||
);
|
||||
if (!!options.gasPrice) {
|
||||
gasPrice = options.gasPrice;
|
||||
assert.isBigNumber('gasPrice', gasPrice);
|
||||
} else {
|
||||
gasPrice = await this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||
}
|
||||
|
||||
const apiOrders = await this.orderbook.getBatchOrdersAsync(makerAssetDatas, [takerAssetData]);
|
||||
const allOrders = apiOrders.map(orders => orders.map(o => o.order));
|
||||
const allPrunedOrders = allOrders.map((orders, i) => {
|
||||
if (orders.length === 0) {
|
||||
return [
|
||||
dummyOrderUtils.createDummyOrderForSampler(
|
||||
makerAssetDatas[i],
|
||||
takerAssetData,
|
||||
this._contractAddresses.uniswapBridge,
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return sortingUtils.sortOrders(
|
||||
orderPrunerUtils.pruneForUsableSignedOrders(
|
||||
orders,
|
||||
this.permittedOrderFeeTypes,
|
||||
this.expiryBufferMs,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const swapQuotes = await this._swapQuoteCalculator.calculateBatchMarketBuySwapQuoteAsync(
|
||||
allPrunedOrders,
|
||||
makerAssetBuyAmount,
|
||||
slippagePercentage,
|
||||
gasPrice,
|
||||
calculateSwapQuoteOpts,
|
||||
);
|
||||
return swapQuotes;
|
||||
}
|
||||
/**
|
||||
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
|
||||
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.
|
||||
|
@ -24,12 +24,14 @@ export const SELL_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Da
|
||||
export const BUY_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
|
||||
|
||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
runLimit: 4096,
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
runLimit: 2 ** 15,
|
||||
excludedSources: [],
|
||||
bridgeSlippage: 0.0005,
|
||||
dustFractionThreshold: 0.01,
|
||||
numSamples: 10,
|
||||
dustFractionThreshold: 0.0025,
|
||||
numSamples: 13,
|
||||
noConflicts: true,
|
||||
sampleDistributionBase: 1.05,
|
||||
};
|
||||
|
||||
export const constants = {
|
||||
@ -40,5 +42,5 @@ export const constants = {
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
ERC20_PROXY_ID: '0xf47261b0',
|
||||
WALLET_SIGNATURE: '0x04',
|
||||
SAMPLER_CONTRACT_GAS_LIMIT: 10e6,
|
||||
SAMPLER_CONTRACT_GAS_LIMIT: 16e6,
|
||||
};
|
||||
|
@ -3,11 +3,17 @@ import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
import { SignedOrderWithFillableAmounts } from '../../types';
|
||||
import { sortingUtils } from '../../utils/sorting_utils';
|
||||
|
||||
import { constants as marketOperationUtilConstants } from './constants';
|
||||
import { AggregationError, ERC20BridgeSource, Fill, FillData, NativeFillData, OrderDomain } from './types';
|
||||
import {
|
||||
AggregationError,
|
||||
CollapsedFill,
|
||||
ERC20BridgeSource,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
const { NULL_BYTES, NULL_ADDRESS, ZERO_AMOUNT } = constants;
|
||||
const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstants;
|
||||
@ -24,23 +30,21 @@ export class CreateOrderUtils {
|
||||
orderDomain: OrderDomain,
|
||||
inputToken: string,
|
||||
outputToken: string,
|
||||
path: Fill[],
|
||||
path: CollapsedFill[],
|
||||
bridgeSlippage: number,
|
||||
): SignedOrderWithFillableAmounts[] {
|
||||
const orders: SignedOrderWithFillableAmounts[] = [];
|
||||
): OptimizedMarketOrder[] {
|
||||
const orders: OptimizedMarketOrder[] = [];
|
||||
for (const fill of path) {
|
||||
const source = (fill.fillData as FillData).source;
|
||||
if (source === ERC20BridgeSource.Native) {
|
||||
orders.push((fill.fillData as NativeFillData).order);
|
||||
if (fill.source === ERC20BridgeSource.Native) {
|
||||
orders.push(createNativeOrder(fill));
|
||||
} else {
|
||||
orders.push(
|
||||
createBridgeOrder(
|
||||
orderDomain,
|
||||
this._getBridgeAddressFromSource(source),
|
||||
fill,
|
||||
this._getBridgeAddressFromSource(fill.source),
|
||||
outputToken,
|
||||
inputToken,
|
||||
fill.output,
|
||||
fill.input,
|
||||
bridgeSlippage,
|
||||
),
|
||||
);
|
||||
@ -54,23 +58,21 @@ export class CreateOrderUtils {
|
||||
orderDomain: OrderDomain,
|
||||
inputToken: string,
|
||||
outputToken: string,
|
||||
path: Fill[],
|
||||
path: CollapsedFill[],
|
||||
bridgeSlippage: number,
|
||||
): SignedOrderWithFillableAmounts[] {
|
||||
const orders: SignedOrderWithFillableAmounts[] = [];
|
||||
): OptimizedMarketOrder[] {
|
||||
const orders: OptimizedMarketOrder[] = [];
|
||||
for (const fill of path) {
|
||||
const source = (fill.fillData as FillData).source;
|
||||
if (source === ERC20BridgeSource.Native) {
|
||||
orders.push((fill.fillData as NativeFillData).order);
|
||||
if (fill.source === ERC20BridgeSource.Native) {
|
||||
orders.push(createNativeOrder(fill));
|
||||
} else {
|
||||
orders.push(
|
||||
createBridgeOrder(
|
||||
orderDomain,
|
||||
this._getBridgeAddressFromSource(source),
|
||||
fill,
|
||||
this._getBridgeAddressFromSource(fill.source),
|
||||
inputToken,
|
||||
outputToken,
|
||||
fill.input,
|
||||
fill.output,
|
||||
bridgeSlippage,
|
||||
true,
|
||||
),
|
||||
@ -97,14 +99,13 @@ export class CreateOrderUtils {
|
||||
|
||||
function createBridgeOrder(
|
||||
orderDomain: OrderDomain,
|
||||
fill: CollapsedFill,
|
||||
bridgeAddress: string,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerAssetAmount: BigNumber,
|
||||
takerAssetAmount: BigNumber,
|
||||
slippage: number,
|
||||
isBuy: boolean = false,
|
||||
): SignedOrderWithFillableAmounts {
|
||||
): OptimizedMarketOrder {
|
||||
return {
|
||||
makerAddress: bridgeAddress,
|
||||
makerAssetData: assetDataUtils.encodeERC20BridgeAssetData(
|
||||
@ -113,7 +114,7 @@ function createBridgeOrder(
|
||||
createBridgeData(takerToken),
|
||||
),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
|
||||
...createCommonOrderFields(orderDomain, makerAssetAmount, takerAssetAmount, slippage, isBuy),
|
||||
...createCommonOrderFields(orderDomain, fill, slippage, isBuy),
|
||||
};
|
||||
}
|
||||
|
||||
@ -123,24 +124,24 @@ function createBridgeData(tokenAddress: string): string {
|
||||
}
|
||||
|
||||
type CommonOrderFields = Pick<
|
||||
SignedOrderWithFillableAmounts,
|
||||
Exclude<keyof SignedOrderWithFillableAmounts, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
|
||||
OptimizedMarketOrder,
|
||||
Exclude<keyof OptimizedMarketOrder, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
|
||||
>;
|
||||
|
||||
function createCommonOrderFields(
|
||||
orderDomain: OrderDomain,
|
||||
makerAssetAmount: BigNumber,
|
||||
takerAssetAmount: BigNumber,
|
||||
fill: CollapsedFill,
|
||||
slippage: number,
|
||||
isBuy: boolean = false,
|
||||
): CommonOrderFields {
|
||||
const makerAssetAmountAdjustedWithSlippage = isBuy
|
||||
? makerAssetAmount
|
||||
: makerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN);
|
||||
? fill.totalMakerAssetAmount
|
||||
: fill.totalMakerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN);
|
||||
const takerAssetAmountAdjustedWithSlippage = isBuy
|
||||
? takerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP)
|
||||
: takerAssetAmount;
|
||||
? fill.totalTakerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP)
|
||||
: fill.totalTakerAssetAmount;
|
||||
return {
|
||||
fill,
|
||||
takerAddress: NULL_ADDRESS,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
feeRecipientAddress: NULL_ADDRESS,
|
||||
@ -159,3 +160,15 @@ function createCommonOrderFields(
|
||||
...orderDomain,
|
||||
};
|
||||
}
|
||||
|
||||
function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder {
|
||||
return {
|
||||
fill: {
|
||||
source: fill.source,
|
||||
totalMakerAssetAmount: fill.totalMakerAssetAmount,
|
||||
totalTakerAssetAmount: fill.totalTakerAssetAmount,
|
||||
subFills: fill.subFills,
|
||||
},
|
||||
...(fill as NativeCollapsedFill).nativeOrder,
|
||||
};
|
||||
}
|
||||
|
@ -14,12 +14,16 @@ import { comparePathOutputs, FillsOptimizer, getPathOutput } from './fill_optimi
|
||||
import { DexOrderSampler } from './sampler';
|
||||
import {
|
||||
AggregationError,
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
FillData,
|
||||
FillFlags,
|
||||
GetMarketOrdersOpts,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
OptimizedMarketOrder,
|
||||
OrderDomain,
|
||||
} from './types';
|
||||
|
||||
@ -53,7 +57,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedOrder[],
|
||||
takerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<SignedOrderWithFillableAmounts[]> {
|
||||
): Promise<OptimizedMarketOrder[]> {
|
||||
if (nativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
@ -63,7 +67,7 @@ export class MarketOperationUtils {
|
||||
};
|
||||
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketSellAsync(
|
||||
nativeOrders,
|
||||
DexOrderSampler.getSampleAmounts(takerAmount, _opts.numSamples),
|
||||
DexOrderSampler.getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
difference(SELL_SOURCES, _opts.excludedSources),
|
||||
);
|
||||
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
|
||||
@ -77,8 +81,7 @@ export class MarketOperationUtils {
|
||||
takerAmount,
|
||||
_opts.dustFractionThreshold,
|
||||
);
|
||||
const clippedNativePath = clipPathToInput(prunedNativePath, takerAmount);
|
||||
|
||||
const clippedNativePath = clipPathToInput(sortFillsByPrice(prunedNativePath), takerAmount);
|
||||
const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts);
|
||||
const allPaths = [...dexPaths];
|
||||
const allFills = flattenDexPaths(dexPaths);
|
||||
@ -106,7 +109,7 @@ export class MarketOperationUtils {
|
||||
this._orderDomain,
|
||||
inputToken,
|
||||
outputToken,
|
||||
simplifyPath(optimalPath),
|
||||
collapsePath(optimalPath, false),
|
||||
_opts.bridgeSlippage,
|
||||
);
|
||||
}
|
||||
@ -123,7 +126,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedOrder[],
|
||||
makerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<SignedOrderWithFillableAmounts[]> {
|
||||
): Promise<OptimizedMarketOrder[]> {
|
||||
if (nativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
@ -134,29 +137,86 @@ export class MarketOperationUtils {
|
||||
|
||||
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketBuyAsync(
|
||||
nativeOrders,
|
||||
DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples),
|
||||
DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase),
|
||||
difference(BUY_SOURCES, _opts.excludedSources),
|
||||
);
|
||||
const signedOrderWithFillableAmounts = this._createBuyOrdersPathFromSamplerResultIfExists(
|
||||
nativeOrders,
|
||||
makerAmount,
|
||||
fillableAmounts,
|
||||
dexQuotes,
|
||||
_opts,
|
||||
);
|
||||
if (!signedOrderWithFillableAmounts) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
return signedOrderWithFillableAmounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the orders required for a batch of market buy operations by (potentially) merging native orders with
|
||||
* generated bridge orders.
|
||||
* @param batchNativeOrders Batch of Native orders.
|
||||
* @param makerAmounts Array amount of maker asset to buy for each batch.
|
||||
* @param opts Options object.
|
||||
* @return orders.
|
||||
*/
|
||||
public async getBatchMarketBuyOrdersAsync(
|
||||
batchNativeOrders: SignedOrder[][],
|
||||
makerAmounts: BigNumber[],
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<Array<OptimizedMarketOrder[] | undefined>> {
|
||||
if (batchNativeOrders.length === 0) {
|
||||
throw new Error(AggregationError.EmptyOrders);
|
||||
}
|
||||
const _opts = {
|
||||
...DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
...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) =>
|
||||
this._createBuyOrdersPathFromSamplerResultIfExists(
|
||||
batchNativeOrders[i],
|
||||
makerAmounts[i],
|
||||
fillableAmounts,
|
||||
dexQuotes,
|
||||
_opts,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _createBuyOrdersPathFromSamplerResultIfExists(
|
||||
nativeOrders: SignedOrder[],
|
||||
makerAmount: BigNumber,
|
||||
nativeOrderFillableAmounts: BigNumber[],
|
||||
dexQuotes: DexSample[][],
|
||||
opts: GetMarketOrdersOpts,
|
||||
): OptimizedMarketOrder[] | undefined {
|
||||
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
|
||||
nativeOrders,
|
||||
fillableAmounts,
|
||||
nativeOrderFillableAmounts,
|
||||
MarketOperation.Buy,
|
||||
);
|
||||
const prunedNativePath = pruneDustFillsFromNativePath(
|
||||
createBuyPathFromNativeOrders(nativeOrdersWithFillableAmounts),
|
||||
makerAmount,
|
||||
_opts.dustFractionThreshold,
|
||||
opts.dustFractionThreshold,
|
||||
);
|
||||
const clippedNativePath = clipPathToInput(prunedNativePath, makerAmount);
|
||||
const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts);
|
||||
const clippedNativePath = clipPathToInput(sortFillsByPrice(prunedNativePath).reverse(), makerAmount);
|
||||
const dexPaths = createPathsFromDexQuotes(dexQuotes, opts.noConflicts);
|
||||
const allPaths = [...dexPaths];
|
||||
const allFills = flattenDexPaths(dexPaths);
|
||||
// If native orders are allowed, splice them in.
|
||||
if (!_opts.excludedSources.includes(ERC20BridgeSource.Native)) {
|
||||
if (!opts.excludedSources.includes(ERC20BridgeSource.Native)) {
|
||||
allPaths.splice(0, 0, clippedNativePath);
|
||||
allFills.splice(0, 0, ...clippedNativePath);
|
||||
}
|
||||
const optimizer = new FillsOptimizer(_opts.runLimit, true);
|
||||
const optimizer = new FillsOptimizer(opts.runLimit, true);
|
||||
const upperBoundPath = pickBestUpperBoundPath(allPaths, makerAmount, true);
|
||||
const optimalPath = optimizer.optimize(
|
||||
// Sorting the orders by price effectively causes the optimizer to walk
|
||||
@ -167,15 +227,15 @@ export class MarketOperationUtils {
|
||||
upperBoundPath,
|
||||
);
|
||||
if (!optimalPath) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
return undefined;
|
||||
}
|
||||
const [inputToken, outputToken] = getOrderTokens(nativeOrders[0]);
|
||||
return this._createOrderUtils.createBuyOrdersFromPath(
|
||||
this._orderDomain,
|
||||
inputToken,
|
||||
outputToken,
|
||||
simplifyPath(optimalPath),
|
||||
_opts.bridgeSlippage,
|
||||
collapsePath(optimalPath, true),
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -186,26 +246,24 @@ function createSignedOrdersWithFillableAmounts(
|
||||
operation: MarketOperation,
|
||||
): SignedOrderWithFillableAmounts[] {
|
||||
return signedOrders
|
||||
.map(
|
||||
(order: SignedOrder, i: number): SignedOrderWithFillableAmounts => {
|
||||
const fillableAmount = fillableAmounts[i];
|
||||
const fillableMakerAssetAmount =
|
||||
operation === MarketOperation.Buy
|
||||
? fillableAmount
|
||||
: orderCalculationUtils.getMakerFillAmount(order, fillableAmount);
|
||||
const fillableTakerAssetAmount =
|
||||
operation === MarketOperation.Sell
|
||||
? fillableAmount
|
||||
: orderCalculationUtils.getTakerFillAmount(order, fillableAmount);
|
||||
const fillableTakerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, fillableTakerAssetAmount);
|
||||
return {
|
||||
fillableMakerAssetAmount,
|
||||
fillableTakerAssetAmount,
|
||||
fillableTakerFeeAmount,
|
||||
...order,
|
||||
};
|
||||
},
|
||||
)
|
||||
.map((order: SignedOrder, i: number) => {
|
||||
const fillableAmount = fillableAmounts[i];
|
||||
const fillableMakerAssetAmount =
|
||||
operation === MarketOperation.Buy
|
||||
? fillableAmount
|
||||
: orderCalculationUtils.getMakerFillAmount(order, fillableAmount);
|
||||
const fillableTakerAssetAmount =
|
||||
operation === MarketOperation.Sell
|
||||
? fillableAmount
|
||||
: orderCalculationUtils.getTakerFillAmount(order, fillableAmount);
|
||||
const fillableTakerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, fillableTakerAssetAmount);
|
||||
return {
|
||||
fillableMakerAssetAmount,
|
||||
fillableTakerAssetAmount,
|
||||
fillableTakerFeeAmount,
|
||||
...order,
|
||||
};
|
||||
})
|
||||
.filter(order => {
|
||||
return !order.fillableMakerAssetAmount.isZero() && !order.fillableTakerAssetAmount.isZero();
|
||||
});
|
||||
@ -356,23 +414,31 @@ function getPathInput(path: Fill[]): BigNumber {
|
||||
}
|
||||
|
||||
// Merges contiguous fills from the same DEX.
|
||||
function simplifyPath(path: Fill[]): Fill[] {
|
||||
const simplified: Fill[] = [];
|
||||
function collapsePath(path: Fill[], isBuy: boolean): CollapsedFill[] {
|
||||
const collapsed: Array<CollapsedFill | NativeCollapsedFill> = [];
|
||||
for (const fill of path) {
|
||||
const makerAssetAmount = isBuy ? fill.input : fill.output;
|
||||
const takerAssetAmount = isBuy ? fill.output : fill.input;
|
||||
const source = (fill.fillData as FillData).source;
|
||||
if (simplified.length !== 0 && source !== ERC20BridgeSource.Native) {
|
||||
const prevFill = simplified[simplified.length - 1];
|
||||
const prevSource = (prevFill.fillData as FillData).source;
|
||||
if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) {
|
||||
const prevFill = collapsed[collapsed.length - 1];
|
||||
// If the last fill is from the same source, merge them.
|
||||
if (prevSource === source) {
|
||||
prevFill.input = prevFill.input.plus(fill.input);
|
||||
prevFill.output = prevFill.output.plus(fill.output);
|
||||
if (prevFill.source === source) {
|
||||
prevFill.totalMakerAssetAmount = prevFill.totalMakerAssetAmount.plus(makerAssetAmount);
|
||||
prevFill.totalTakerAssetAmount = prevFill.totalTakerAssetAmount.plus(takerAssetAmount);
|
||||
prevFill.subFills.push({ makerAssetAmount, takerAssetAmount });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
simplified.push(fill);
|
||||
collapsed.push({
|
||||
source: fill.fillData.source,
|
||||
totalMakerAssetAmount: makerAssetAmount,
|
||||
totalTakerAssetAmount: takerAssetAmount,
|
||||
subFills: [{ makerAssetAmount, takerAssetAmount }],
|
||||
nativeOrder: (fill.fillData as NativeFillData).order,
|
||||
});
|
||||
}
|
||||
return simplified;
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
// Sort fills by descending price.
|
||||
|
@ -13,16 +13,14 @@ export class DexOrderSampler {
|
||||
/**
|
||||
* Generate sample amounts up to `maxFillAmount`.
|
||||
*/
|
||||
public static getSampleAmounts(maxFillAmount: BigNumber, numSamples: number): BigNumber[] {
|
||||
const amounts = [];
|
||||
for (let i = 0; i < numSamples; i++) {
|
||||
amounts.push(
|
||||
maxFillAmount
|
||||
.times(i + 1)
|
||||
.div(numSamples)
|
||||
.integerValue(BigNumber.ROUND_UP),
|
||||
);
|
||||
}
|
||||
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);
|
||||
});
|
||||
return amounts;
|
||||
}
|
||||
|
||||
@ -50,6 +48,36 @@ export class DexOrderSampler {
|
||||
return [fillableAmount, quotes];
|
||||
}
|
||||
|
||||
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[],
|
||||
|
@ -79,6 +79,48 @@ export interface Fill {
|
||||
fillData: FillData | NativeFillData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents continguous fills on a path that have been merged together.
|
||||
*/
|
||||
export interface CollapsedFill {
|
||||
/**
|
||||
* The source DEX.
|
||||
*/
|
||||
source: ERC20BridgeSource;
|
||||
/**
|
||||
* Total maker asset amount.
|
||||
*/
|
||||
totalMakerAssetAmount: BigNumber;
|
||||
/**
|
||||
* Total taker asset amount.
|
||||
*/
|
||||
totalTakerAssetAmount: BigNumber;
|
||||
/**
|
||||
* All the fill asset amounts that were collapsed into this node.
|
||||
*/
|
||||
subFills: Array<{
|
||||
makerAssetAmount: BigNumber;
|
||||
takerAssetAmount: BigNumber;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `CollapsedFill` wrapping a native order.
|
||||
*/
|
||||
export interface NativeCollapsedFill extends CollapsedFill {
|
||||
nativeOrder: SignedOrderWithFillableAmounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized orders to fill.
|
||||
*/
|
||||
export interface OptimizedMarketOrder extends SignedOrderWithFillableAmounts {
|
||||
/**
|
||||
* The optimized fills that generated this order.
|
||||
*/
|
||||
fill: CollapsedFill;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`.
|
||||
*/
|
||||
@ -114,4 +156,12 @@ export interface GetMarketOrdersOpts {
|
||||
* Default is 0.01 (100 basis points).
|
||||
*/
|
||||
dustFractionThreshold: number;
|
||||
/**
|
||||
* The exponential sampling distribution base.
|
||||
* A value of 1 will result in evenly spaced samples.
|
||||
* > 1 will result in more samples at lower sizes.
|
||||
* < 1 will result in more samples at higher sizes.
|
||||
* Default: 1.25.
|
||||
*/
|
||||
sampleDistributionBase: number;
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ export class ProtocolFeeUtils {
|
||||
public gasPriceEstimation: BigNumber;
|
||||
private readonly _gasPriceHeart: any;
|
||||
|
||||
constructor(gasPricePollingIntervalInMs: number) {
|
||||
constructor(gasPricePollingIntervalInMs: number, initialGasPrice: BigNumber = constants.ZERO_AMOUNT) {
|
||||
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
|
||||
this.gasPriceEstimation = constants.ZERO_AMOUNT;
|
||||
this.gasPriceEstimation = initialGasPrice;
|
||||
this._initializeHeartBeat();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
|
||||
import { fillableAmountsUtils } from './fillable_amounts_utils';
|
||||
import { MarketOperationUtils } from './market_operation_utils';
|
||||
import { ERC20BridgeSource, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { ProtocolFeeUtils } from './protocol_fee_utils';
|
||||
import { utils } from './utils';
|
||||
|
||||
@ -63,6 +64,58 @@ export class SwapQuoteCalculator {
|
||||
)) as MarketBuySwapQuote;
|
||||
}
|
||||
|
||||
public async calculateBatchMarketBuySwapQuoteAsync(
|
||||
batchPrunedOrders: SignedOrder[][],
|
||||
takerAssetFillAmounts: BigNumber[],
|
||||
slippagePercentage: number,
|
||||
gasPrice: BigNumber,
|
||||
opts: CalculateSwapQuoteOpts,
|
||||
): Promise<Array<MarketBuySwapQuote | undefined>> {
|
||||
return (await this._calculateBatchBuySwapQuoteAsync(
|
||||
batchPrunedOrders,
|
||||
takerAssetFillAmounts,
|
||||
slippagePercentage,
|
||||
gasPrice,
|
||||
MarketOperation.Buy,
|
||||
opts,
|
||||
)) as Array<MarketBuySwapQuote | undefined>;
|
||||
}
|
||||
|
||||
private async _calculateBatchBuySwapQuoteAsync(
|
||||
batchPrunedOrders: SignedOrder[][],
|
||||
assetFillAmounts: BigNumber[],
|
||||
slippagePercentage: number,
|
||||
gasPrice: BigNumber,
|
||||
operation: MarketOperation,
|
||||
opts: CalculateSwapQuoteOpts,
|
||||
): Promise<Array<SwapQuote | undefined>> {
|
||||
const assetFillAmountsWithSlippage = assetFillAmounts.map(a =>
|
||||
a.plus(a.multipliedBy(slippagePercentage).integerValue()),
|
||||
);
|
||||
const batchSignedOrders = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync(
|
||||
batchPrunedOrders,
|
||||
assetFillAmountsWithSlippage,
|
||||
opts,
|
||||
);
|
||||
const batchSwapQuotes = await Promise.all(
|
||||
batchSignedOrders.map(async (orders, i) => {
|
||||
if (orders) {
|
||||
const { makerAssetData, takerAssetData } = batchPrunedOrders[i][0];
|
||||
return this._createSwapQuoteAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
orders,
|
||||
operation,
|
||||
assetFillAmounts[i],
|
||||
gasPrice,
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
return batchSwapQuotes;
|
||||
}
|
||||
private async _calculateSwapQuoteAsync(
|
||||
prunedOrders: SignedOrder[],
|
||||
assetFillAmount: BigNumber,
|
||||
@ -74,7 +127,7 @@ export class SwapQuoteCalculator {
|
||||
// since prunedOrders do not have fillState, we will add a buffer of fillable orders to consider that some native are orders are partially filled
|
||||
|
||||
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
|
||||
let resultOrders: SignedOrderWithFillableAmounts[] = [];
|
||||
let resultOrders: OptimizedMarketOrder[] = [];
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
@ -91,28 +144,43 @@ export class SwapQuoteCalculator {
|
||||
}
|
||||
|
||||
// assetData information for the result
|
||||
const takerAssetData = prunedOrders[0].takerAssetData;
|
||||
const makerAssetData = prunedOrders[0].makerAssetData;
|
||||
|
||||
const { makerAssetData, takerAssetData } = prunedOrders[0];
|
||||
return this._createSwapQuoteAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
resultOrders,
|
||||
operation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
);
|
||||
}
|
||||
private async _createSwapQuoteAsync(
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
resultOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
): Promise<SwapQuote> {
|
||||
const bestCaseQuoteInfo = await this._calculateQuoteInfoAsync(
|
||||
createBestCaseOrders(resultOrders, operation, opts.bridgeSlippage),
|
||||
resultOrders,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
operation,
|
||||
);
|
||||
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts
|
||||
// such that they are sorted from worst rate to best rate
|
||||
const worstCaseQuoteInfo = await this._calculateQuoteInfoAsync(
|
||||
_.reverse(resultOrders.slice()),
|
||||
resultOrders,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
operation,
|
||||
true,
|
||||
);
|
||||
|
||||
const quoteBase = {
|
||||
takerAssetData,
|
||||
makerAssetData,
|
||||
orders: resultOrders,
|
||||
// Remove fill metadata.
|
||||
orders: resultOrders.map(o => _.omit(o, 'fill')) as SignedOrderWithFillableAmounts[],
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
gasPrice,
|
||||
@ -135,31 +203,39 @@ export class SwapQuoteCalculator {
|
||||
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
private async _calculateQuoteInfoAsync(
|
||||
prunedOrders: SignedOrderWithFillableAmounts[],
|
||||
orders: OptimizedMarketOrder[],
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
operation: MarketOperation,
|
||||
worstCase: boolean = false,
|
||||
): Promise<SwapQuoteInfo> {
|
||||
if (operation === MarketOperation.Buy) {
|
||||
return this._calculateMarketBuyQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice);
|
||||
return this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
|
||||
} else {
|
||||
return this._calculateMarketSellQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice);
|
||||
return this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
|
||||
}
|
||||
}
|
||||
|
||||
private async _calculateMarketSellQuoteInfoAsync(
|
||||
prunedOrders: SignedOrderWithFillableAmounts[],
|
||||
orders: OptimizedMarketOrder[],
|
||||
takerAssetSellAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
worstCase: boolean = false,
|
||||
): Promise<SwapQuoteInfo> {
|
||||
const result = prunedOrders.reduce(
|
||||
(acc, order) => {
|
||||
const {
|
||||
totalMakerAssetAmount,
|
||||
totalTakerAssetAmount,
|
||||
totalFeeTakerAssetAmount,
|
||||
remainingTakerAssetFillAmount,
|
||||
} = acc;
|
||||
let totalMakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let totalTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let totalFeeTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let remainingTakerAssetFillAmount = takerAssetSellAmount;
|
||||
const filledOrders = [] as OptimizedMarketOrder[];
|
||||
const _orders = !worstCase ? orders : orders.slice().reverse();
|
||||
for (const order of _orders) {
|
||||
let makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let feeTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
if (remainingTakerAssetFillAmount.lte(0)) {
|
||||
break;
|
||||
}
|
||||
if (order.fill.source === ERC20BridgeSource.Native) {
|
||||
const adjustedFillableMakerAssetAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterFees(
|
||||
order,
|
||||
);
|
||||
@ -170,99 +246,155 @@ export class SwapQuoteCalculator {
|
||||
remainingTakerAssetFillAmount,
|
||||
adjustedFillableTakerAssetAmount,
|
||||
);
|
||||
const { takerAssetAmount, feeTakerAssetAmount } = getTakerAssetAmountBreakDown(
|
||||
order,
|
||||
takerAssetAmountWithFees,
|
||||
);
|
||||
const makerAssetAmount = takerAssetAmountWithFees
|
||||
const takerAssetAmountBreakDown = getTakerAssetAmountBreakDown(order, takerAssetAmountWithFees);
|
||||
takerAssetAmount = takerAssetAmountBreakDown.takerAssetAmount;
|
||||
feeTakerAssetAmount = takerAssetAmountBreakDown.feeTakerAssetAmount;
|
||||
makerAssetAmount = takerAssetAmountWithFees
|
||||
.div(adjustedFillableTakerAssetAmount)
|
||||
.multipliedBy(adjustedFillableMakerAssetAmount)
|
||||
.times(adjustedFillableMakerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
return {
|
||||
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerAssetAmount),
|
||||
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount),
|
||||
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount),
|
||||
remainingTakerAssetFillAmount: BigNumber.max(
|
||||
constants.ZERO_AMOUNT,
|
||||
remainingTakerAssetFillAmount.minus(takerAssetAmountWithFees),
|
||||
),
|
||||
};
|
||||
},
|
||||
{
|
||||
totalMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
totalTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
remainingTakerAssetFillAmount: takerAssetSellAmount,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// This is a collapsed bridge order.
|
||||
// Because collapsed bridge orders actually fill at different rates,
|
||||
// we can iterate over the uncollapsed fills to get the actual
|
||||
// asset amounts transfered.
|
||||
// We can also assume there are no fees and the order is not
|
||||
// partially filled.
|
||||
|
||||
// Infer the bridge slippage from the difference between the fill
|
||||
// size and the actual order asset amounts.
|
||||
const makerAssetBridgeSlippage = !worstCase
|
||||
? constants.ONE_AMOUNT
|
||||
: order.makerAssetAmount.div(order.fill.totalMakerAssetAmount);
|
||||
const takerAssetBridgeSlippage = !worstCase
|
||||
? constants.ONE_AMOUNT
|
||||
: order.takerAssetAmount.div(order.fill.totalTakerAssetAmount);
|
||||
// Consecutively fill the subfills in this order.
|
||||
const subFills = !worstCase ? order.fill.subFills : order.fill.subFills.slice().reverse();
|
||||
for (const subFill of subFills) {
|
||||
if (remainingTakerAssetFillAmount.minus(takerAssetAmount).lte(0)) {
|
||||
break;
|
||||
}
|
||||
const partialTakerAssetAmount = subFill.takerAssetAmount.times(takerAssetBridgeSlippage);
|
||||
const partialMakerAssetAmount = subFill.makerAssetAmount.times(makerAssetBridgeSlippage);
|
||||
const partialTakerAssetFillAmount = BigNumber.min(
|
||||
partialTakerAssetAmount,
|
||||
remainingTakerAssetFillAmount.minus(takerAssetAmount),
|
||||
);
|
||||
const partialMakerAssetFillAmount = partialTakerAssetFillAmount
|
||||
.div(partialTakerAssetAmount)
|
||||
.times(partialMakerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
takerAssetAmount = takerAssetAmount.plus(partialTakerAssetFillAmount);
|
||||
makerAssetAmount = makerAssetAmount.plus(partialMakerAssetFillAmount);
|
||||
}
|
||||
}
|
||||
totalMakerAssetAmount = totalMakerAssetAmount.plus(makerAssetAmount);
|
||||
totalTakerAssetAmount = totalTakerAssetAmount.plus(takerAssetAmount);
|
||||
totalFeeTakerAssetAmount = totalFeeTakerAssetAmount.plus(feeTakerAssetAmount);
|
||||
remainingTakerAssetFillAmount = remainingTakerAssetFillAmount
|
||||
.minus(takerAssetAmount)
|
||||
.minus(feeTakerAssetAmount);
|
||||
filledOrders.push(order);
|
||||
}
|
||||
const protocolFeeInWeiAmount = await this._protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(
|
||||
prunedOrders,
|
||||
filledOrders,
|
||||
gasPrice,
|
||||
);
|
||||
return {
|
||||
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
|
||||
takerAssetAmount: result.totalTakerAssetAmount,
|
||||
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
|
||||
makerAssetAmount: result.totalMakerAssetAmount,
|
||||
feeTakerAssetAmount: totalFeeTakerAssetAmount,
|
||||
takerAssetAmount: totalTakerAssetAmount,
|
||||
totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
|
||||
makerAssetAmount: totalMakerAssetAmount,
|
||||
protocolFeeInWeiAmount,
|
||||
};
|
||||
}
|
||||
|
||||
private async _calculateMarketBuyQuoteInfoAsync(
|
||||
prunedOrders: SignedOrderWithFillableAmounts[],
|
||||
orders: OptimizedMarketOrder[],
|
||||
makerAssetBuyAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
worstCase: boolean = false,
|
||||
): Promise<SwapQuoteInfo> {
|
||||
const result = prunedOrders.reduce(
|
||||
(acc, order) => {
|
||||
const {
|
||||
totalMakerAssetAmount,
|
||||
totalTakerAssetAmount,
|
||||
totalFeeTakerAssetAmount,
|
||||
remainingMakerAssetFillAmount,
|
||||
} = acc;
|
||||
let totalMakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let totalTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let totalFeeTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let remainingMakerAssetFillAmount = makerAssetBuyAmount;
|
||||
const filledOrders = [] as OptimizedMarketOrder[];
|
||||
const _orders = !worstCase ? orders : orders.slice().reverse();
|
||||
for (const order of _orders) {
|
||||
let makerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let takerAssetAmount = constants.ZERO_AMOUNT;
|
||||
let feeTakerAssetAmount = constants.ZERO_AMOUNT;
|
||||
if (remainingMakerAssetFillAmount.lte(0)) {
|
||||
break;
|
||||
}
|
||||
if (order.fill.source === ERC20BridgeSource.Native) {
|
||||
const adjustedFillableMakerAssetAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterFees(
|
||||
order,
|
||||
);
|
||||
const adjustedFillableTakerAssetAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterFees(
|
||||
order,
|
||||
);
|
||||
const makerFillAmount = BigNumber.min(remainingMakerAssetFillAmount, adjustedFillableMakerAssetAmount);
|
||||
const takerAssetAmountWithFees = makerFillAmount
|
||||
makerAssetAmount = BigNumber.min(remainingMakerAssetFillAmount, adjustedFillableMakerAssetAmount);
|
||||
const takerAssetAmountWithFees = makerAssetAmount
|
||||
.div(adjustedFillableMakerAssetAmount)
|
||||
.multipliedBy(adjustedFillableTakerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_UP);
|
||||
const takerAssetAmountBreakDown = getTakerAssetAmountBreakDown(order, takerAssetAmountWithFees);
|
||||
takerAssetAmount = takerAssetAmountBreakDown.takerAssetAmount;
|
||||
feeTakerAssetAmount = takerAssetAmountBreakDown.feeTakerAssetAmount;
|
||||
} else {
|
||||
// This is a collapsed bridge order.
|
||||
// Because collapsed bridge orders actually fill at different rates,
|
||||
// we can iterate over the uncollapsed fills to get the actual
|
||||
// asset amounts transfered.
|
||||
// We can also assume there are no fees and the order is not
|
||||
// partially filled.
|
||||
|
||||
const { takerAssetAmount, feeTakerAssetAmount } = getTakerAssetAmountBreakDown(
|
||||
order,
|
||||
takerAssetAmountWithFees,
|
||||
);
|
||||
return {
|
||||
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerFillAmount),
|
||||
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount),
|
||||
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount),
|
||||
remainingMakerAssetFillAmount: BigNumber.max(
|
||||
constants.ZERO_AMOUNT,
|
||||
remainingMakerAssetFillAmount.minus(makerFillAmount),
|
||||
),
|
||||
};
|
||||
},
|
||||
{
|
||||
totalMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
totalTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
remainingMakerAssetFillAmount: makerAssetBuyAmount,
|
||||
},
|
||||
);
|
||||
// Infer the bridge slippage from the difference between the fill
|
||||
// size and the actual order asset amounts.
|
||||
const makerAssetBridgeSlippage = !worstCase
|
||||
? constants.ONE_AMOUNT
|
||||
: order.makerAssetAmount.div(order.fill.totalMakerAssetAmount);
|
||||
const takerAssetBridgeSlippage = !worstCase
|
||||
? constants.ONE_AMOUNT
|
||||
: order.takerAssetAmount.div(order.fill.totalTakerAssetAmount);
|
||||
// Consecutively fill the subfills in this order.
|
||||
const subFills = !worstCase ? order.fill.subFills : order.fill.subFills.slice().reverse();
|
||||
for (const subFill of subFills) {
|
||||
if (remainingMakerAssetFillAmount.minus(makerAssetAmount).lte(0)) {
|
||||
break;
|
||||
}
|
||||
const partialTakerAssetAmount = subFill.takerAssetAmount.times(takerAssetBridgeSlippage);
|
||||
const partialMakerAssetAmount = subFill.makerAssetAmount.times(makerAssetBridgeSlippage);
|
||||
const partialMakerAssetFillAmount = BigNumber.min(
|
||||
partialMakerAssetAmount,
|
||||
remainingMakerAssetFillAmount.minus(makerAssetAmount),
|
||||
);
|
||||
const partialTakerAssetFillAmount = partialMakerAssetFillAmount
|
||||
.div(partialMakerAssetAmount)
|
||||
.times(partialTakerAssetAmount)
|
||||
.integerValue(BigNumber.ROUND_UP);
|
||||
takerAssetAmount = takerAssetAmount.plus(partialTakerAssetFillAmount);
|
||||
makerAssetAmount = makerAssetAmount.plus(partialMakerAssetFillAmount);
|
||||
}
|
||||
}
|
||||
totalMakerAssetAmount = totalMakerAssetAmount.plus(makerAssetAmount);
|
||||
totalTakerAssetAmount = totalTakerAssetAmount.plus(takerAssetAmount);
|
||||
totalFeeTakerAssetAmount = totalFeeTakerAssetAmount.plus(feeTakerAssetAmount);
|
||||
remainingMakerAssetFillAmount = remainingMakerAssetFillAmount.minus(makerAssetAmount);
|
||||
filledOrders.push(order);
|
||||
}
|
||||
const protocolFeeInWeiAmount = await this._protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(
|
||||
prunedOrders,
|
||||
filledOrders,
|
||||
gasPrice,
|
||||
);
|
||||
return {
|
||||
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
|
||||
takerAssetAmount: result.totalTakerAssetAmount,
|
||||
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
|
||||
makerAssetAmount: result.totalMakerAssetAmount,
|
||||
feeTakerAssetAmount: totalFeeTakerAssetAmount,
|
||||
takerAssetAmount: totalTakerAssetAmount,
|
||||
totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
|
||||
makerAssetAmount: totalMakerAssetAmount,
|
||||
protocolFeeInWeiAmount,
|
||||
};
|
||||
}
|
||||
@ -303,35 +435,3 @@ function getTakerAssetAmountBreakDown(
|
||||
takerAssetAmount: takerAssetAmountWithFees,
|
||||
};
|
||||
}
|
||||
|
||||
function createBestCaseOrders(
|
||||
orders: SignedOrderWithFillableAmounts[],
|
||||
operation: MarketOperation,
|
||||
bridgeSlippage: number,
|
||||
): SignedOrderWithFillableAmounts[] {
|
||||
// Scale the asset amounts to undo the bridge slippage applied to
|
||||
// bridge orders.
|
||||
const bestCaseOrders: SignedOrderWithFillableAmounts[] = [];
|
||||
for (const order of orders) {
|
||||
if (!order.makerAssetData.startsWith(constants.BRIDGE_ASSET_DATA_PREFIX)) {
|
||||
bestCaseOrders.push(order);
|
||||
continue;
|
||||
}
|
||||
bestCaseOrders.push({
|
||||
...order,
|
||||
...(operation === MarketOperation.Sell
|
||||
? {
|
||||
makerAssetAmount: order.makerAssetAmount
|
||||
.dividedBy(1 - bridgeSlippage)
|
||||
.integerValue(BigNumber.ROUND_DOWN),
|
||||
}
|
||||
: // Buy operation
|
||||
{
|
||||
takerAssetAmount: order.takerAssetAmount
|
||||
.dividedBy(bridgeSlippage + 1)
|
||||
.integerValue(BigNumber.ROUND_UP),
|
||||
}),
|
||||
});
|
||||
}
|
||||
return bestCaseOrders;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ describe('#calculateLiquidity', () => {
|
||||
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||
prunedSignedOrders,
|
||||
);
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10));
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(11));
|
||||
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(9));
|
||||
});
|
||||
it('should provide correct liquidity result with orders with takerFees in takerAsset', () => {
|
||||
@ -31,7 +31,7 @@ describe('#calculateLiquidity', () => {
|
||||
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||
prunedSignedOrders,
|
||||
);
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10));
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(11));
|
||||
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(15));
|
||||
});
|
||||
it('should provide correct liquidity result with orders with takerFees in makerAsset', () => {
|
||||
@ -51,7 +51,7 @@ describe('#calculateLiquidity', () => {
|
||||
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||
prunedSignedOrders,
|
||||
);
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(25));
|
||||
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(27));
|
||||
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(33));
|
||||
});
|
||||
});
|
||||
|
@ -123,7 +123,7 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
||||
};
|
||||
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS, new BigNumber(1));
|
||||
expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
|
||||
erc20TakerTokenContract,
|
||||
makerAddress,
|
||||
|
@ -126,7 +126,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
||||
};
|
||||
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS, new BigNumber(1));
|
||||
expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory(
|
||||
erc20TokenContract,
|
||||
makerAddress,
|
||||
|
@ -306,7 +306,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_SAMPLER = createSamplerFromSellRates(SOURCE_RATES);
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0 };
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0, sampleDistributionBase: 1 };
|
||||
const defaultMarketOperationUtils = new MarketOperationUtils(
|
||||
DEFAULT_SAMPLER,
|
||||
contractAddresses,
|
||||
@ -552,7 +552,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
|
||||
);
|
||||
const DEFAULT_SAMPLER = createSamplerFromBuyRates(SOURCE_RATES);
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0 };
|
||||
const DEFAULT_OPTS = { numSamples: 3, runLimit: 0, sampleDistributionBase: 1 };
|
||||
const defaultMarketOperationUtils = new MarketOperationUtils(
|
||||
DEFAULT_SAMPLER,
|
||||
contractAddresses,
|
||||
|
@ -65,6 +65,9 @@ const createSamplerFromSignedOrdersWithFillableAmounts = (
|
||||
return sampler;
|
||||
};
|
||||
|
||||
// TODO(dorothy-zbornak): Replace these tests entirely with unit tests because
|
||||
// omg they're a nightmare to maintain.
|
||||
|
||||
// tslint:disable:max-file-line-count
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
describe('swapQuoteCalculator', () => {
|
||||
@ -272,7 +275,7 @@ describe('swapQuoteCalculator', () => {
|
||||
});
|
||||
});
|
||||
it('calculates a correct swapQuote with slippage (feeless orders)', async () => {
|
||||
const assetSellAmount = baseUnitAmount(1);
|
||||
const assetSellAmount = baseUnitAmount(4);
|
||||
const slippagePercentage = 0.2;
|
||||
const sampler = createSamplerFromSignedOrdersWithFillableAmounts(
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS,
|
||||
@ -289,10 +292,10 @@ describe('swapQuoteCalculator', () => {
|
||||
GAS_PRICE,
|
||||
CALCULATE_SWAP_QUOTE_OPTS,
|
||||
);
|
||||
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[2],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[1],
|
||||
]);
|
||||
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
|
||||
@ -301,15 +304,15 @@ describe('swapQuoteCalculator', () => {
|
||||
feeTakerAssetAmount: baseUnitAmount(0),
|
||||
takerAssetAmount: assetSellAmount,
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(6),
|
||||
makerAssetAmount: baseUnitAmount(9),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(0),
|
||||
takerAssetAmount: assetSellAmount,
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(0.4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
makerAssetAmount: baseUnitAmount(1.6),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
});
|
||||
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', async () => {
|
||||
@ -372,7 +375,7 @@ describe('swapQuoteCalculator', () => {
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[2],
|
||||
]);
|
||||
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
|
||||
// test if rates are correct
|
||||
@ -381,14 +384,14 @@ describe('swapQuoteCalculator', () => {
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2.25)),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(4.5),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(0.5),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(0.5)),
|
||||
feeTakerAssetAmount: baseUnitAmount(1.2),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.2)),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(1),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
makerAssetAmount: baseUnitAmount(1.8),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
});
|
||||
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', async () => {
|
||||
@ -411,23 +414,24 @@ describe('swapQuoteCalculator', () => {
|
||||
);
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[2],
|
||||
]);
|
||||
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
|
||||
// test if rates are correct
|
||||
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
|
||||
feeTakerAssetAmount: baseUnitAmount(1.5).minus(1),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.5)).plus(1),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(0.8),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
makerAssetAmount: baseUnitAmount(4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
|
||||
feeTakerAssetAmount: baseUnitAmount(1.5).minus(1),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.5)).plus(1),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(0.8),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
makerAssetAmount: baseUnitAmount(4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
});
|
||||
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', async () => {
|
||||
@ -451,24 +455,24 @@ describe('swapQuoteCalculator', () => {
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[2],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0],
|
||||
]);
|
||||
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
|
||||
// test if rates are correct
|
||||
// 50 takerAsset units to fill the first order + 100 takerAsset units for fees
|
||||
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(1.5).minus(1),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.5)).plus(1),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(0.8),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2),
|
||||
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
|
||||
totalTakerAssetAmount: assetSellAmount,
|
||||
makerAssetAmount: baseUnitAmount(3.6),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -678,7 +682,7 @@ describe('swapQuoteCalculator', () => {
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[2],
|
||||
]);
|
||||
expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
|
||||
|
||||
@ -692,12 +696,16 @@ describe('swapQuoteCalculator', () => {
|
||||
takerAssetAmount,
|
||||
totalTakerAssetAmount: takerAssetAmount,
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(0),
|
||||
takerAssetAmount: baseUnitAmount(5.5),
|
||||
totalTakerAssetAmount: baseUnitAmount(5.5),
|
||||
takerAssetAmount: baseUnitAmount(20)
|
||||
.div(6)
|
||||
.integerValue(BigNumber.ROUND_UP),
|
||||
totalTakerAssetAmount: baseUnitAmount(20)
|
||||
.div(6)
|
||||
.integerValue(BigNumber.ROUND_UP),
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
@ -766,7 +774,7 @@ describe('swapQuoteCalculator', () => {
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET[2],
|
||||
]);
|
||||
expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
|
||||
// test if rates are correct
|
||||
@ -775,12 +783,16 @@ describe('swapQuoteCalculator', () => {
|
||||
takerAssetAmount: fiveSixthEthInWei,
|
||||
totalTakerAssetAmount: baseUnitAmount(2.5).plus(fiveSixthEthInWei),
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2.5),
|
||||
takerAssetAmount: baseUnitAmount(5.5),
|
||||
totalTakerAssetAmount: baseUnitAmount(8),
|
||||
feeTakerAssetAmount: baseUnitAmount(3),
|
||||
takerAssetAmount: baseUnitAmount(10)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 3.3333...
|
||||
totalTakerAssetAmount: baseUnitAmount(19)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 6.3333...
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
@ -805,21 +817,33 @@ describe('swapQuoteCalculator', () => {
|
||||
);
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[1],
|
||||
]);
|
||||
expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
|
||||
// test if rates are correct
|
||||
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2.5),
|
||||
takerAssetAmount: baseUnitAmount(2.5),
|
||||
totalTakerAssetAmount: baseUnitAmount(5),
|
||||
feeTakerAssetAmount: baseUnitAmount(0.5)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.16666...
|
||||
takerAssetAmount: baseUnitAmount(0.5)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.1666...
|
||||
totalTakerAssetAmount: baseUnitAmount(1)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.3333...
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2.5),
|
||||
takerAssetAmount: baseUnitAmount(2.5),
|
||||
totalTakerAssetAmount: baseUnitAmount(5),
|
||||
feeTakerAssetAmount: baseUnitAmount(0.5)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.16666...
|
||||
takerAssetAmount: baseUnitAmount(0.5)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.1666...
|
||||
totalTakerAssetAmount: baseUnitAmount(1)
|
||||
.div(3)
|
||||
.integerValue(BigNumber.ROUND_UP), // 0.3333...
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
@ -845,14 +869,14 @@ describe('swapQuoteCalculator', () => {
|
||||
// test if orders are correct
|
||||
expect(swapQuote.orders).to.deep.equal([
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[1],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0],
|
||||
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[2],
|
||||
]);
|
||||
expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
|
||||
// test if rates are correct
|
||||
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
|
||||
feeTakerAssetAmount: baseUnitAmount(2.75),
|
||||
takerAssetAmount: baseUnitAmount(2.75),
|
||||
totalTakerAssetAmount: baseUnitAmount(5.5),
|
||||
feeTakerAssetAmount: baseUnitAmount(1.25).minus(1),
|
||||
takerAssetAmount: baseUnitAmount(2.25).plus(1),
|
||||
totalTakerAssetAmount: baseUnitAmount(3.5),
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
});
|
||||
@ -879,7 +903,7 @@ describe('swapQuoteCalculator', () => {
|
||||
.multipliedBy(0.1)
|
||||
.integerValue(BigNumber.ROUND_CEIL),
|
||||
makerAssetAmount: assetBuyAmount,
|
||||
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
|
||||
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -119,7 +119,7 @@ describe('swapQuoteConsumerUtils', () => {
|
||||
};
|
||||
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS);
|
||||
protocolFeeUtils = new ProtocolFeeUtils(constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS, new BigNumber(1));
|
||||
forwarderOrderFactory = new OrderFactory(privateKey, defaultForwarderOrderParams);
|
||||
|
||||
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
|
||||
|
@ -51,7 +51,7 @@ const PARTIAL_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS: Array<Partial<SignedOrderWit
|
||||
takerAssetAmount: baseUnitAmount(6),
|
||||
makerAssetAmount: baseUnitAmount(6),
|
||||
fillableTakerAssetAmount: baseUnitAmount(3),
|
||||
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||
fillableMakerAssetAmount: baseUnitAmount(3),
|
||||
},
|
||||
...PARTIAL_ORDER,
|
||||
},
|
||||
@ -86,7 +86,7 @@ const PARTIAL_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET: Array<Partial<Sig
|
||||
makerAssetAmount: baseUnitAmount(6),
|
||||
takerFee: baseUnitAmount(4),
|
||||
fillableTakerAssetAmount: baseUnitAmount(3),
|
||||
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||
fillableMakerAssetAmount: baseUnitAmount(3),
|
||||
fillableTakerFeeAmount: baseUnitAmount(2),
|
||||
},
|
||||
...PARTIAL_ORDER_FEE_IN_TAKER_ASSET,
|
||||
|
@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Update `DevUtils` mainnet, Ropsten, Rinkeby, Kovan addresses",
|
||||
"pr": 2436
|
||||
},
|
||||
{
|
||||
"note": "Update `ERC20BridgeSampler` address on kovan and mainnet.",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -22,7 +22,7 @@
|
||||
"devUtils": "0x5f53f2aa72cb3a9371bf3c58e8fb3a313478b2f4",
|
||||
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
|
||||
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
|
||||
"erc20BridgeSampler": "0x1b402fdb5ee87f989c11e3963557e89cc313b6c0",
|
||||
"erc20BridgeSampler": "0x25840bf3582cb9e5acabbf45148b3092ac3f6b56",
|
||||
"kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62",
|
||||
"eth2DaiBridge": "0xe97ea901d034ba2e018155264f77c417ce7717f9",
|
||||
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",
|
||||
@ -110,7 +110,7 @@
|
||||
"erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64",
|
||||
"uniswapBridge": "0x0000000000000000000000000000000000000000",
|
||||
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x551f0e213dcb71f676558d8b0ab559d1cdd103f2",
|
||||
"erc20BridgeSampler": "0x8c9f255253bcf2c9539c887fee4d71c69fd035b9",
|
||||
"kyberBridge": "0x0000000000000000000000000000000000000000",
|
||||
"chaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"dydxBridge": "0x0000000000000000000000000000000000000000"
|
||||
|
@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Update all artifacts.",
|
||||
"pr": 2432
|
||||
},
|
||||
{
|
||||
"note": "Update `IERC20BridgeSampler artifact",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -71,6 +71,94 @@
|
||||
"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": "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": [
|
||||
@ -196,6 +284,26 @@
|
||||
},
|
||||
"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": {
|
||||
|
@ -13,6 +13,10 @@
|
||||
{
|
||||
"note": "Regenerate wrappers to catch empty reverts on live networks.",
|
||||
"pr": 2433
|
||||
},
|
||||
{
|
||||
"note": "Update `IERC20BridgeSampler`",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -18,7 +18,7 @@
|
||||
"rebuild": "yarn wrappers:clean && yarn wrappers:generate && yarn wrappers:prettier && yarn build",
|
||||
"build:ci": "yarn build",
|
||||
"lint": "tslint --format stylish --project . --exclude **/lib/**/*",
|
||||
"fix": "tslint --fix --format stylish --project .--exclude **/lib/**/*",
|
||||
"fix": "tslint --fix --format stylish --project . --exclude **/lib/**/*",
|
||||
"prettier": "prettier --write **/* --config ../../.prettierrc",
|
||||
"clean": "shx rm -rf lib generated_docs",
|
||||
"docs_test": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --out generated_docs ./src/generated-wrappers/*",
|
||||
|
@ -281,6 +281,204 @@ export class IERC20BridgeSamplerContract extends BaseContract {
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: 'orders',
|
||||
type: 'tuple[][]',
|
||||
components: [
|
||||
{
|
||||
name: 'makerAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'takerAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'feeRecipientAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'senderAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'makerAssetAmount',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'takerAssetAmount',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'makerFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'takerFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'expirationTimeSeconds',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'salt',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'makerAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'takerAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'makerFeeAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'takerFeeAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'orderSignatures',
|
||||
type: 'bytes[][]',
|
||||
},
|
||||
{
|
||||
name: 'sources',
|
||||
type: 'address[]',
|
||||
},
|
||||
{
|
||||
name: 'makerTokenAmounts',
|
||||
type: 'uint256[][]',
|
||||
},
|
||||
],
|
||||
name: 'queryBatchOrdersAndSampleBuys',
|
||||
outputs: [
|
||||
{
|
||||
name: 'ordersAndSamples',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{
|
||||
name: 'orderFillableAssetAmounts',
|
||||
type: 'uint256[]',
|
||||
},
|
||||
{
|
||||
name: 'tokenAmountsBySource',
|
||||
type: 'uint256[][]',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: 'orders',
|
||||
type: 'tuple[][]',
|
||||
components: [
|
||||
{
|
||||
name: 'makerAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'takerAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'feeRecipientAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'senderAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'makerAssetAmount',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'takerAssetAmount',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'makerFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'takerFee',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'expirationTimeSeconds',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'salt',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'makerAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'takerAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'makerFeeAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
{
|
||||
name: 'takerFeeAssetData',
|
||||
type: 'bytes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'orderSignatures',
|
||||
type: 'bytes[][]',
|
||||
},
|
||||
{
|
||||
name: 'sources',
|
||||
type: 'address[]',
|
||||
},
|
||||
{
|
||||
name: 'takerTokenAmounts',
|
||||
type: 'uint256[][]',
|
||||
},
|
||||
],
|
||||
name: 'queryBatchOrdersAndSampleSells',
|
||||
outputs: [
|
||||
{
|
||||
name: 'ordersAndSamples',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{
|
||||
name: 'orderFillableAssetAmounts',
|
||||
type: 'uint256[]',
|
||||
},
|
||||
{
|
||||
name: 'tokenAmountsBySource',
|
||||
type: 'uint256[][]',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
@ -654,6 +852,118 @@ export class IERC20BridgeSamplerContract extends BaseContract {
|
||||
},
|
||||
};
|
||||
}
|
||||
public queryBatchOrdersAndSampleBuys(
|
||||
orders: Array<
|
||||
Array<{
|
||||
makerAddress: string;
|
||||
takerAddress: string;
|
||||
feeRecipientAddress: string;
|
||||
senderAddress: string;
|
||||
makerAssetAmount: BigNumber;
|
||||
takerAssetAmount: BigNumber;
|
||||
makerFee: BigNumber;
|
||||
takerFee: BigNumber;
|
||||
expirationTimeSeconds: BigNumber;
|
||||
salt: BigNumber;
|
||||
makerAssetData: string;
|
||||
takerAssetData: string;
|
||||
makerFeeAssetData: string;
|
||||
takerFeeAssetData: string;
|
||||
}>
|
||||
>,
|
||||
orderSignatures: string[][],
|
||||
sources: string[],
|
||||
makerTokenAmounts: BigNumber[][],
|
||||
): ContractFunctionObj<Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>> {
|
||||
const self = (this as any) as IERC20BridgeSamplerContract;
|
||||
assert.isArray('orders', orders);
|
||||
assert.isArray('orderSignatures', orderSignatures);
|
||||
assert.isArray('sources', sources);
|
||||
assert.isArray('makerTokenAmounts', makerTokenAmounts);
|
||||
const functionSignature =
|
||||
'queryBatchOrdersAndSampleBuys((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[][])';
|
||||
|
||||
return {
|
||||
async callAsync(
|
||||
callData: Partial<CallData> = {},
|
||||
defaultBlock?: BlockParam,
|
||||
): Promise<Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>> {
|
||||
BaseContract._assertCallParams(callData, defaultBlock);
|
||||
const rawCallResult = await self._performCallAsync(
|
||||
{ ...callData, data: this.getABIEncodedTransactionData() },
|
||||
defaultBlock,
|
||||
);
|
||||
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||
return abiEncoder.strictDecodeReturnValue<
|
||||
Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>
|
||||
>(rawCallResult);
|
||||
},
|
||||
getABIEncodedTransactionData(): string {
|
||||
return self._strictEncodeArguments(functionSignature, [
|
||||
orders,
|
||||
orderSignatures,
|
||||
sources,
|
||||
makerTokenAmounts,
|
||||
]);
|
||||
},
|
||||
};
|
||||
}
|
||||
public queryBatchOrdersAndSampleSells(
|
||||
orders: Array<
|
||||
Array<{
|
||||
makerAddress: string;
|
||||
takerAddress: string;
|
||||
feeRecipientAddress: string;
|
||||
senderAddress: string;
|
||||
makerAssetAmount: BigNumber;
|
||||
takerAssetAmount: BigNumber;
|
||||
makerFee: BigNumber;
|
||||
takerFee: BigNumber;
|
||||
expirationTimeSeconds: BigNumber;
|
||||
salt: BigNumber;
|
||||
makerAssetData: string;
|
||||
takerAssetData: string;
|
||||
makerFeeAssetData: string;
|
||||
takerFeeAssetData: string;
|
||||
}>
|
||||
>,
|
||||
orderSignatures: string[][],
|
||||
sources: string[],
|
||||
takerTokenAmounts: BigNumber[][],
|
||||
): ContractFunctionObj<Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>> {
|
||||
const self = (this as any) as IERC20BridgeSamplerContract;
|
||||
assert.isArray('orders', orders);
|
||||
assert.isArray('orderSignatures', orderSignatures);
|
||||
assert.isArray('sources', sources);
|
||||
assert.isArray('takerTokenAmounts', takerTokenAmounts);
|
||||
const functionSignature =
|
||||
'queryBatchOrdersAndSampleSells((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[][])';
|
||||
|
||||
return {
|
||||
async callAsync(
|
||||
callData: Partial<CallData> = {},
|
||||
defaultBlock?: BlockParam,
|
||||
): Promise<Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>> {
|
||||
BaseContract._assertCallParams(callData, defaultBlock);
|
||||
const rawCallResult = await self._performCallAsync(
|
||||
{ ...callData, data: this.getABIEncodedTransactionData() },
|
||||
defaultBlock,
|
||||
);
|
||||
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||
return abiEncoder.strictDecodeReturnValue<
|
||||
Array<{ orderFillableAssetAmounts: BigNumber[]; tokenAmountsBySource: BigNumber[][] }>
|
||||
>(rawCallResult);
|
||||
},
|
||||
getABIEncodedTransactionData(): string {
|
||||
return self._strictEncodeArguments(functionSignature, [
|
||||
orders,
|
||||
orderSignatures,
|
||||
sources,
|
||||
takerTokenAmounts,
|
||||
]);
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Query native orders and sample buy quotes on multiple DEXes at once.
|
||||
* @param orders Native orders to query.
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added `getBatchOrdersAsync` for fetching batches of orders",
|
||||
"pr": 2427
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1578272714,
|
||||
"version": "2.0.1",
|
||||
|
@ -25,6 +25,19 @@ export class OrderStore {
|
||||
}
|
||||
return orderSet;
|
||||
}
|
||||
public async getBatchOrderSetsForAssetsAsync(
|
||||
makerAssetDatas: string[],
|
||||
takerAssetDatas: string[],
|
||||
): Promise<OrderSet[]> {
|
||||
const orderSets: OrderSet[] = [];
|
||||
for (const makerAssetData of makerAssetDatas) {
|
||||
for (const takerAssetData of takerAssetDatas) {
|
||||
const orderSet = await this.getOrderSetForAssetsAsync(makerAssetData, takerAssetData);
|
||||
orderSets.push(orderSet);
|
||||
}
|
||||
}
|
||||
return orderSets;
|
||||
}
|
||||
public async updateAsync(addedRemoved: AddedRemovedOrders): Promise<void> {
|
||||
const { added, removed, assetPairKey } = addedRemoved;
|
||||
const orders = await this.getOrderSetForAssetPairAsync(assetPairKey);
|
||||
|
@ -84,6 +84,26 @@ export class Orderbook {
|
||||
o => o.order.makerAssetData === makerAssetData && o.order.takerAssetData === takerAssetData,
|
||||
);
|
||||
}
|
||||
public async getBatchOrdersAsync(makerAssetDatas: string[], takerAssetDatas: string[]): Promise<APIOrder[][]> {
|
||||
for (const [mi, makerAssetData] of makerAssetDatas.entries()) {
|
||||
for (const [ti, takerAssetData] of makerAssetDatas.entries()) {
|
||||
assert.isString(`makerAssetDatas[${mi}]`, makerAssetData);
|
||||
assert.isString(`takerAssetDatas[${ti}]`, takerAssetData);
|
||||
const assetPairKey = OrderStore.getKeyForAssetPair(makerAssetData, takerAssetData);
|
||||
if (!(await this._orderStore.hasAsync(assetPairKey))) {
|
||||
await this._orderProvider.createSubscriptionForAssetPairAsync(makerAssetData, takerAssetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
const orderSets = await this._orderStore.getBatchOrderSetsForAssetsAsync(makerAssetDatas, takerAssetDatas);
|
||||
return orderSets.map(orderSet =>
|
||||
Array.from(orderSet.values()).filter(
|
||||
o =>
|
||||
makerAssetDatas.includes(o.order.makerAssetData) &&
|
||||
takerAssetDatas.includes(o.order.takerAssetData),
|
||||
),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Returns all of the Available Asset Pairs for the provided Order Provider.
|
||||
*/
|
||||
|
@ -117,17 +117,11 @@ class CleanCommandExtension(clean):
|
||||
)
|
||||
):
|
||||
try:
|
||||
remove(
|
||||
path.join(
|
||||
"src",
|
||||
"zero_ex",
|
||||
"contract_wrappers",
|
||||
contract,
|
||||
"__init__.py",
|
||||
)
|
||||
)
|
||||
print(f"Removing {contract}...", end="")
|
||||
remove(contract)
|
||||
print("done")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
print("file not found")
|
||||
|
||||
|
||||
class TestPublishCommand(distutils.command.build_py.build_py):
|
||||
|
Loading…
x
Reference in New Issue
Block a user