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:
Jacob Evans 2020-01-22 11:54:45 +10:00 committed by GitHub
commit 4e46bf4697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1609 additions and 327 deletions

View File

@ -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", "version": "1.0.3",
"changes": [ "changes": [

View File

@ -37,9 +37,73 @@ contract ERC20BridgeSampler is
DeploymentConstants DeploymentConstants
{ {
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); 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 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. /// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
@ -281,6 +345,8 @@ contract ERC20BridgeSampler is
uint256 rate = 0; uint256 rate = 0;
if (didSucceed) { if (didSucceed) {
rate = abi.decode(resultData, (uint256)); rate = abi.decode(resultData, (uint256));
} else {
break;
} }
makerTokenAmounts[i] = makerTokenAmounts[i] =
rate * rate *
@ -321,6 +387,8 @@ contract ERC20BridgeSampler is
uint256 buyAmount = 0; uint256 buyAmount = 0;
if (didSucceed) { if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256)); buyAmount = abi.decode(resultData, (uint256));
} else{
break;
} }
makerTokenAmounts[i] = buyAmount; makerTokenAmounts[i] = buyAmount;
} }
@ -356,6 +424,8 @@ contract ERC20BridgeSampler is
uint256 sellAmount = 0; uint256 sellAmount = 0;
if (didSucceed) { if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256)); sellAmount = abi.decode(resultData, (uint256));
} else {
break;
} }
takerTokenAmounts[i] = sellAmount; takerTokenAmounts[i] = sellAmount;
} }
@ -384,26 +454,28 @@ contract ERC20BridgeSampler is
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == _getWethAddress()) { if (makerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector, takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else if (takerToken == _getWethAddress()) { } else if (takerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector, makerTokenExchange.getEthToTokenInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else { } else {
uint256 ethBought = _callUniswapExchangePriceFunction( uint256 ethBought;
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector, takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
if (ethBought != 0) { if (ethBought != 0) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector, makerTokenExchange.getEthToTokenInputPrice.selector,
ethBought ethBought
@ -412,6 +484,9 @@ contract ERC20BridgeSampler is
makerTokenAmounts[i] = 0; makerTokenAmounts[i] = 0;
} }
} }
if (!didSucceed) {
break;
}
} }
} }
@ -438,26 +513,28 @@ contract ERC20BridgeSampler is
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == _getWethAddress()) { if (makerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector, takerTokenExchange.getTokenToEthOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else if (takerToken == _getWethAddress()) { } else if (takerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector, makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else { } else {
uint256 ethSold = _callUniswapExchangePriceFunction( uint256 ethSold;
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector, makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
if (ethSold != 0) { if (ethSold != 0) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector, takerTokenExchange.getTokenToEthOutputPrice.selector,
ethSold ethSold
@ -466,6 +543,9 @@ contract ERC20BridgeSampler is
takerTokenAmounts[i] = 0; takerTokenAmounts[i] = 0;
} }
} }
if (!didSucceed) {
break;
}
} }
} }
@ -493,12 +573,13 @@ contract ERC20BridgeSampler is
) )
private private
view view
returns (uint256 outputAmount) returns (uint256 outputAmount, bool didSucceed)
{ {
if (uniswapExchangeAddress == address(0)) { 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)( uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
functionSelector, functionSelector,

View File

@ -23,6 +23,52 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
interface IERC20BridgeSampler { interface IERC20BridgeSampler {
struct OrdersAndSample {
uint256[] orderFillableAssetAmounts;
uint256[][] tokenAmountsBySource;
}
/// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once.
/// @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. /// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.

View File

@ -338,7 +338,11 @@ contract TestERC20BridgeSampler is
bytes32 orderHash = keccak256(abi.encode(order.salt)); bytes32 orderHash = keccak256(abi.encode(order.salt));
// Everything else is derived from the hash. // Everything else is derived from the hash.
orderInfo.orderHash = orderHash; orderInfo.orderHash = orderHash;
orderInfo.orderStatus = 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; orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount;
fillableTakerAssetAmount = fillableTakerAssetAmount =
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount; order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;

View File

@ -195,7 +195,7 @@ blockchainTests('erc20-bridge-sampler', env => {
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber { function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
const hash = getPackedHash(hexUtils.toHex(order.salt, 32)); 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(); const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
if (orderStatus !== 3 || !isValidSignature) { if (orderStatus !== 3 || !isValidSignature) {
return constants.ZERO_AMOUNT; return constants.ZERO_AMOUNT;
@ -208,7 +208,7 @@ blockchainTests('erc20-bridge-sampler', env => {
return order.makerAssetAmount return order.makerAssetAmount
.times(takerAmount) .times(takerAmount)
.div(order.takerAssetAmount) .div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_DOWN); .integerValue(BigNumber.ROUND_UP);
} }
function getERC20AssetData(tokenAddress: string): string { function getERC20AssetData(tokenAddress: string): string {
@ -255,7 +255,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('getOrderFillableTakerAssetAmounts()', () => { describe('getOrderFillableTakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => { it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); 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 expected = orders.map(getDeterministicFillableTakerAssetAmount);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected); 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 () => { it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT; 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(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); 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 () => { it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT; 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(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -293,7 +293,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('getOrderFillableMakerAssetAmounts()', () => { describe('getOrderFillableMakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => { it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); 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 expected = orders.map(getDeterministicFillableMakerAssetAmount);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected); 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 () => { it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT; 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(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); 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 () => { it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT; 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(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -330,7 +330,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('queryOrdersAndSampleSells()', () => { describe('queryOrdersAndSampleSells()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); 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 () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
@ -436,7 +436,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('queryOrdersAndSampleBuys()', () => { describe('queryOrdersAndSampleBuys()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); 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 () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();

View File

@ -277,10 +277,16 @@ for (const abiFileName of abiFileNames) {
// use command-line tool black to reformat, if its available // use command-line tool black to reformat, if its available
try { try {
execSync(`black --line-length 79 ${outFilePath}`); execSync(`black --line-length 79 ${outFilePath}`);
} catch { } catch (e) {
const BLACK_RC_CANNOT_PARSE = 123; // empirical black exit code
if (e.status === BLACK_RC_CANNOT_PARSE) {
logUtils.warn( logUtils.warn(
'Failed to reformat generated Python with black. Do you have it installed? Proceeding anyways...', '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...');
}
} }
} }

View File

@ -195,7 +195,7 @@ export const utils = {
return tuple.internalType return tuple.internalType
.replace('struct ', '') .replace('struct ', '')
.replace('.', '') .replace('.', '')
.replace('[]', ''); .replace(/\[\]/g, '');
} else { } else {
const tupleComponents = tuple.components; const tupleComponents = tuple.components;
const lengthOfHashSuffix = 8; const lengthOfHashSuffix = 8;

File diff suppressed because one or more lines are too long

View File

@ -138,6 +138,8 @@ contract AbiGenDummy
} }
function methodReturningArrayOfStructs() public pure returns(Struct[] memory) {} function methodReturningArrayOfStructs() public pure returns(Struct[] memory) {}
function methodAcceptingArrayOfStructs(Struct[] memory) public pure {}
function methodAcceptingArrayOfArrayOfStructs(Struct[][] memory) public pure {}
struct NestedStruct { struct NestedStruct {
Struct innerStruct; Struct innerStruct;

File diff suppressed because one or more lines are too long

View File

@ -90,7 +90,7 @@ class LibDummy:
try: try:
for middleware in MIDDLEWARE: for middleware in MIDDLEWARE:
web3.middleware_onion.inject( web3.middleware_onion.inject(
middleware["function"], layer=middleware["layer"] middleware["function"], layer=middleware["layer"],
) )
except ValueError as value_error: except ValueError as value_error:
if value_error.args == ( if value_error.args == (

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,18 @@
{ {
"note": "Remove `getSmartContractParamsOrThrow()` from `SwapQuoteConsumer`s.", "note": "Remove `getSmartContractParamsOrThrow()` from `SwapQuoteConsumer`s.",
"pr": 2432 "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
} }
] ]
}, },

View File

@ -54,6 +54,11 @@ export {
SwapQuoteConsumerError, SwapQuoteConsumerError,
SignedOrderWithFillableAmounts, SignedOrderWithFillableAmounts,
} from './types'; } 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 { affiliateFeeUtils } from './utils/affiliate_fee_utils';
export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils';

View File

@ -226,6 +226,58 @@ export class SwapQuoter {
options, options,
)) as MarketBuySwapQuote; )) 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. * 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. * You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.

View File

@ -24,12 +24,14 @@ export const SELL_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Da
export const BUY_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai]; export const BUY_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
runLimit: 4096, // tslint:disable-next-line: custom-no-magic-numbers
runLimit: 2 ** 15,
excludedSources: [], excludedSources: [],
bridgeSlippage: 0.0005, bridgeSlippage: 0.0005,
dustFractionThreshold: 0.01, dustFractionThreshold: 0.0025,
numSamples: 10, numSamples: 13,
noConflicts: true, noConflicts: true,
sampleDistributionBase: 1.05,
}; };
export const constants = { export const constants = {
@ -40,5 +42,5 @@ export const constants = {
DEFAULT_GET_MARKET_ORDERS_OPTS, DEFAULT_GET_MARKET_ORDERS_OPTS,
ERC20_PROXY_ID: '0xf47261b0', ERC20_PROXY_ID: '0xf47261b0',
WALLET_SIGNATURE: '0x04', WALLET_SIGNATURE: '0x04',
SAMPLER_CONTRACT_GAS_LIMIT: 10e6, SAMPLER_CONTRACT_GAS_LIMIT: 16e6,
}; };

View File

@ -3,11 +3,17 @@ import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { AbiEncoder, BigNumber } from '@0x/utils'; import { AbiEncoder, BigNumber } from '@0x/utils';
import { constants } from '../../constants'; import { constants } from '../../constants';
import { SignedOrderWithFillableAmounts } from '../../types';
import { sortingUtils } from '../../utils/sorting_utils'; import { sortingUtils } from '../../utils/sorting_utils';
import { constants as marketOperationUtilConstants } from './constants'; 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 { NULL_BYTES, NULL_ADDRESS, ZERO_AMOUNT } = constants;
const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstants; const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstants;
@ -24,23 +30,21 @@ export class CreateOrderUtils {
orderDomain: OrderDomain, orderDomain: OrderDomain,
inputToken: string, inputToken: string,
outputToken: string, outputToken: string,
path: Fill[], path: CollapsedFill[],
bridgeSlippage: number, bridgeSlippage: number,
): SignedOrderWithFillableAmounts[] { ): OptimizedMarketOrder[] {
const orders: SignedOrderWithFillableAmounts[] = []; const orders: OptimizedMarketOrder[] = [];
for (const fill of path) { for (const fill of path) {
const source = (fill.fillData as FillData).source; if (fill.source === ERC20BridgeSource.Native) {
if (source === ERC20BridgeSource.Native) { orders.push(createNativeOrder(fill));
orders.push((fill.fillData as NativeFillData).order);
} else { } else {
orders.push( orders.push(
createBridgeOrder( createBridgeOrder(
orderDomain, orderDomain,
this._getBridgeAddressFromSource(source), fill,
this._getBridgeAddressFromSource(fill.source),
outputToken, outputToken,
inputToken, inputToken,
fill.output,
fill.input,
bridgeSlippage, bridgeSlippage,
), ),
); );
@ -54,23 +58,21 @@ export class CreateOrderUtils {
orderDomain: OrderDomain, orderDomain: OrderDomain,
inputToken: string, inputToken: string,
outputToken: string, outputToken: string,
path: Fill[], path: CollapsedFill[],
bridgeSlippage: number, bridgeSlippage: number,
): SignedOrderWithFillableAmounts[] { ): OptimizedMarketOrder[] {
const orders: SignedOrderWithFillableAmounts[] = []; const orders: OptimizedMarketOrder[] = [];
for (const fill of path) { for (const fill of path) {
const source = (fill.fillData as FillData).source; if (fill.source === ERC20BridgeSource.Native) {
if (source === ERC20BridgeSource.Native) { orders.push(createNativeOrder(fill));
orders.push((fill.fillData as NativeFillData).order);
} else { } else {
orders.push( orders.push(
createBridgeOrder( createBridgeOrder(
orderDomain, orderDomain,
this._getBridgeAddressFromSource(source), fill,
this._getBridgeAddressFromSource(fill.source),
inputToken, inputToken,
outputToken, outputToken,
fill.input,
fill.output,
bridgeSlippage, bridgeSlippage,
true, true,
), ),
@ -97,14 +99,13 @@ export class CreateOrderUtils {
function createBridgeOrder( function createBridgeOrder(
orderDomain: OrderDomain, orderDomain: OrderDomain,
fill: CollapsedFill,
bridgeAddress: string, bridgeAddress: string,
makerToken: string, makerToken: string,
takerToken: string, takerToken: string,
makerAssetAmount: BigNumber,
takerAssetAmount: BigNumber,
slippage: number, slippage: number,
isBuy: boolean = false, isBuy: boolean = false,
): SignedOrderWithFillableAmounts { ): OptimizedMarketOrder {
return { return {
makerAddress: bridgeAddress, makerAddress: bridgeAddress,
makerAssetData: assetDataUtils.encodeERC20BridgeAssetData( makerAssetData: assetDataUtils.encodeERC20BridgeAssetData(
@ -113,7 +114,7 @@ function createBridgeOrder(
createBridgeData(takerToken), createBridgeData(takerToken),
), ),
takerAssetData: assetDataUtils.encodeERC20AssetData(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< type CommonOrderFields = Pick<
SignedOrderWithFillableAmounts, OptimizedMarketOrder,
Exclude<keyof SignedOrderWithFillableAmounts, 'makerAddress' | 'makerAssetData' | 'takerAssetData'> Exclude<keyof OptimizedMarketOrder, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
>; >;
function createCommonOrderFields( function createCommonOrderFields(
orderDomain: OrderDomain, orderDomain: OrderDomain,
makerAssetAmount: BigNumber, fill: CollapsedFill,
takerAssetAmount: BigNumber,
slippage: number, slippage: number,
isBuy: boolean = false, isBuy: boolean = false,
): CommonOrderFields { ): CommonOrderFields {
const makerAssetAmountAdjustedWithSlippage = isBuy const makerAssetAmountAdjustedWithSlippage = isBuy
? makerAssetAmount ? fill.totalMakerAssetAmount
: makerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN); : fill.totalMakerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN);
const takerAssetAmountAdjustedWithSlippage = isBuy const takerAssetAmountAdjustedWithSlippage = isBuy
? takerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP) ? fill.totalTakerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP)
: takerAssetAmount; : fill.totalTakerAssetAmount;
return { return {
fill,
takerAddress: NULL_ADDRESS, takerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS, senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS, feeRecipientAddress: NULL_ADDRESS,
@ -159,3 +160,15 @@ function createCommonOrderFields(
...orderDomain, ...orderDomain,
}; };
} }
function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder {
return {
fill: {
source: fill.source,
totalMakerAssetAmount: fill.totalMakerAssetAmount,
totalTakerAssetAmount: fill.totalTakerAssetAmount,
subFills: fill.subFills,
},
...(fill as NativeCollapsedFill).nativeOrder,
};
}

View File

@ -14,12 +14,16 @@ import { comparePathOutputs, FillsOptimizer, getPathOutput } from './fill_optimi
import { DexOrderSampler } from './sampler'; import { DexOrderSampler } from './sampler';
import { import {
AggregationError, AggregationError,
CollapsedFill,
DexSample, DexSample,
ERC20BridgeSource, ERC20BridgeSource,
Fill, Fill,
FillData, FillData,
FillFlags, FillFlags,
GetMarketOrdersOpts, GetMarketOrdersOpts,
NativeCollapsedFill,
NativeFillData,
OptimizedMarketOrder,
OrderDomain, OrderDomain,
} from './types'; } from './types';
@ -53,7 +57,7 @@ export class MarketOperationUtils {
nativeOrders: SignedOrder[], nativeOrders: SignedOrder[],
takerAmount: BigNumber, takerAmount: BigNumber,
opts?: Partial<GetMarketOrdersOpts>, opts?: Partial<GetMarketOrdersOpts>,
): Promise<SignedOrderWithFillableAmounts[]> { ): Promise<OptimizedMarketOrder[]> {
if (nativeOrders.length === 0) { if (nativeOrders.length === 0) {
throw new Error(AggregationError.EmptyOrders); throw new Error(AggregationError.EmptyOrders);
} }
@ -63,7 +67,7 @@ export class MarketOperationUtils {
}; };
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketSellAsync( const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketSellAsync(
nativeOrders, nativeOrders,
DexOrderSampler.getSampleAmounts(takerAmount, _opts.numSamples), DexOrderSampler.getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
difference(SELL_SOURCES, _opts.excludedSources), difference(SELL_SOURCES, _opts.excludedSources),
); );
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts( const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
@ -77,8 +81,7 @@ export class MarketOperationUtils {
takerAmount, takerAmount,
_opts.dustFractionThreshold, _opts.dustFractionThreshold,
); );
const clippedNativePath = clipPathToInput(prunedNativePath, takerAmount); const clippedNativePath = clipPathToInput(sortFillsByPrice(prunedNativePath), takerAmount);
const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts); const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts);
const allPaths = [...dexPaths]; const allPaths = [...dexPaths];
const allFills = flattenDexPaths(dexPaths); const allFills = flattenDexPaths(dexPaths);
@ -106,7 +109,7 @@ export class MarketOperationUtils {
this._orderDomain, this._orderDomain,
inputToken, inputToken,
outputToken, outputToken,
simplifyPath(optimalPath), collapsePath(optimalPath, false),
_opts.bridgeSlippage, _opts.bridgeSlippage,
); );
} }
@ -123,7 +126,7 @@ export class MarketOperationUtils {
nativeOrders: SignedOrder[], nativeOrders: SignedOrder[],
makerAmount: BigNumber, makerAmount: BigNumber,
opts?: Partial<GetMarketOrdersOpts>, opts?: Partial<GetMarketOrdersOpts>,
): Promise<SignedOrderWithFillableAmounts[]> { ): Promise<OptimizedMarketOrder[]> {
if (nativeOrders.length === 0) { if (nativeOrders.length === 0) {
throw new Error(AggregationError.EmptyOrders); throw new Error(AggregationError.EmptyOrders);
} }
@ -134,29 +137,86 @@ export class MarketOperationUtils {
const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketBuyAsync( const [fillableAmounts, dexQuotes] = await this._dexSampler.getFillableAmountsAndSampleMarketBuyAsync(
nativeOrders, nativeOrders,
DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples), DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase),
difference(BUY_SOURCES, _opts.excludedSources), 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( const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
nativeOrders, nativeOrders,
fillableAmounts, nativeOrderFillableAmounts,
MarketOperation.Buy, MarketOperation.Buy,
); );
const prunedNativePath = pruneDustFillsFromNativePath( const prunedNativePath = pruneDustFillsFromNativePath(
createBuyPathFromNativeOrders(nativeOrdersWithFillableAmounts), createBuyPathFromNativeOrders(nativeOrdersWithFillableAmounts),
makerAmount, makerAmount,
_opts.dustFractionThreshold, opts.dustFractionThreshold,
); );
const clippedNativePath = clipPathToInput(prunedNativePath, makerAmount); const clippedNativePath = clipPathToInput(sortFillsByPrice(prunedNativePath).reverse(), makerAmount);
const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts); const dexPaths = createPathsFromDexQuotes(dexQuotes, opts.noConflicts);
const allPaths = [...dexPaths]; const allPaths = [...dexPaths];
const allFills = flattenDexPaths(dexPaths); const allFills = flattenDexPaths(dexPaths);
// If native orders are allowed, splice them in. // 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); allPaths.splice(0, 0, clippedNativePath);
allFills.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 upperBoundPath = pickBestUpperBoundPath(allPaths, makerAmount, true);
const optimalPath = optimizer.optimize( const optimalPath = optimizer.optimize(
// Sorting the orders by price effectively causes the optimizer to walk // Sorting the orders by price effectively causes the optimizer to walk
@ -167,15 +227,15 @@ export class MarketOperationUtils {
upperBoundPath, upperBoundPath,
); );
if (!optimalPath) { if (!optimalPath) {
throw new Error(AggregationError.NoOptimalPath); return undefined;
} }
const [inputToken, outputToken] = getOrderTokens(nativeOrders[0]); const [inputToken, outputToken] = getOrderTokens(nativeOrders[0]);
return this._createOrderUtils.createBuyOrdersFromPath( return this._createOrderUtils.createBuyOrdersFromPath(
this._orderDomain, this._orderDomain,
inputToken, inputToken,
outputToken, outputToken,
simplifyPath(optimalPath), collapsePath(optimalPath, true),
_opts.bridgeSlippage, opts.bridgeSlippage,
); );
} }
} }
@ -186,8 +246,7 @@ function createSignedOrdersWithFillableAmounts(
operation: MarketOperation, operation: MarketOperation,
): SignedOrderWithFillableAmounts[] { ): SignedOrderWithFillableAmounts[] {
return signedOrders return signedOrders
.map( .map((order: SignedOrder, i: number) => {
(order: SignedOrder, i: number): SignedOrderWithFillableAmounts => {
const fillableAmount = fillableAmounts[i]; const fillableAmount = fillableAmounts[i];
const fillableMakerAssetAmount = const fillableMakerAssetAmount =
operation === MarketOperation.Buy operation === MarketOperation.Buy
@ -204,8 +263,7 @@ function createSignedOrdersWithFillableAmounts(
fillableTakerFeeAmount, fillableTakerFeeAmount,
...order, ...order,
}; };
}, })
)
.filter(order => { .filter(order => {
return !order.fillableMakerAssetAmount.isZero() && !order.fillableTakerAssetAmount.isZero(); return !order.fillableMakerAssetAmount.isZero() && !order.fillableTakerAssetAmount.isZero();
}); });
@ -356,23 +414,31 @@ function getPathInput(path: Fill[]): BigNumber {
} }
// Merges contiguous fills from the same DEX. // Merges contiguous fills from the same DEX.
function simplifyPath(path: Fill[]): Fill[] { function collapsePath(path: Fill[], isBuy: boolean): CollapsedFill[] {
const simplified: Fill[] = []; const collapsed: Array<CollapsedFill | NativeCollapsedFill> = [];
for (const fill of path) { 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; const source = (fill.fillData as FillData).source;
if (simplified.length !== 0 && source !== ERC20BridgeSource.Native) { if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) {
const prevFill = simplified[simplified.length - 1]; const prevFill = collapsed[collapsed.length - 1];
const prevSource = (prevFill.fillData as FillData).source;
// If the last fill is from the same source, merge them. // If the last fill is from the same source, merge them.
if (prevSource === source) { if (prevFill.source === source) {
prevFill.input = prevFill.input.plus(fill.input); prevFill.totalMakerAssetAmount = prevFill.totalMakerAssetAmount.plus(makerAssetAmount);
prevFill.output = prevFill.output.plus(fill.output); prevFill.totalTakerAssetAmount = prevFill.totalTakerAssetAmount.plus(takerAssetAmount);
prevFill.subFills.push({ makerAssetAmount, takerAssetAmount });
continue; 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. // Sort fills by descending price.

View File

@ -13,16 +13,14 @@ export class DexOrderSampler {
/** /**
* Generate sample amounts up to `maxFillAmount`. * Generate sample amounts up to `maxFillAmount`.
*/ */
public static getSampleAmounts(maxFillAmount: BigNumber, numSamples: number): BigNumber[] { public static getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
const amounts = []; const distribution = [...Array<BigNumber>(numSamples)].map((v, i) => new BigNumber(expBase).pow(i));
for (let i = 0; i < numSamples; i++) { const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution)));
amounts.push( const amounts = stepSizes.map((s, i) => {
maxFillAmount return maxFillAmount
.times(i + 1) .times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)]))
.div(numSamples) .integerValue(BigNumber.ROUND_UP);
.integerValue(BigNumber.ROUND_UP), });
);
}
return amounts; return amounts;
} }
@ -50,6 +48,36 @@ export class DexOrderSampler {
return [fillableAmount, quotes]; 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( public async getFillableAmountsAndSampleMarketSellAsync(
nativeOrders: SignedOrder[], nativeOrders: SignedOrder[],
sampleAmounts: BigNumber[], sampleAmounts: BigNumber[],

View File

@ -79,6 +79,48 @@ export interface Fill {
fillData: FillData | NativeFillData; 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()`. * Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`.
*/ */
@ -114,4 +156,12 @@ export interface GetMarketOrdersOpts {
* Default is 0.01 (100 basis points). * Default is 0.01 (100 basis points).
*/ */
dustFractionThreshold: number; 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;
} }

View File

@ -9,9 +9,9 @@ export class ProtocolFeeUtils {
public gasPriceEstimation: BigNumber; public gasPriceEstimation: BigNumber;
private readonly _gasPriceHeart: any; private readonly _gasPriceHeart: any;
constructor(gasPricePollingIntervalInMs: number) { constructor(gasPricePollingIntervalInMs: number, initialGasPrice: BigNumber = constants.ZERO_AMOUNT) {
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs); this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
this.gasPriceEstimation = constants.ZERO_AMOUNT; this.gasPriceEstimation = initialGasPrice;
this._initializeHeartBeat(); this._initializeHeartBeat();
} }

View File

@ -16,6 +16,7 @@ import {
import { fillableAmountsUtils } from './fillable_amounts_utils'; import { fillableAmountsUtils } from './fillable_amounts_utils';
import { MarketOperationUtils } from './market_operation_utils'; import { MarketOperationUtils } from './market_operation_utils';
import { ERC20BridgeSource, OptimizedMarketOrder } from './market_operation_utils/types';
import { ProtocolFeeUtils } from './protocol_fee_utils'; import { ProtocolFeeUtils } from './protocol_fee_utils';
import { utils } from './utils'; import { utils } from './utils';
@ -63,6 +64,58 @@ export class SwapQuoteCalculator {
)) as MarketBuySwapQuote; )) 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( private async _calculateSwapQuoteAsync(
prunedOrders: SignedOrder[], prunedOrders: SignedOrder[],
assetFillAmount: BigNumber, 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 // 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(); const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
let resultOrders: SignedOrderWithFillableAmounts[] = []; let resultOrders: OptimizedMarketOrder[] = [];
if (operation === MarketOperation.Buy) { if (operation === MarketOperation.Buy) {
resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync( resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync(
@ -91,28 +144,43 @@ export class SwapQuoteCalculator {
} }
// assetData information for the result // assetData information for the result
const takerAssetData = prunedOrders[0].takerAssetData; const { makerAssetData, takerAssetData } = prunedOrders[0];
const makerAssetData = prunedOrders[0].makerAssetData; 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( const bestCaseQuoteInfo = await this._calculateQuoteInfoAsync(
createBestCaseOrders(resultOrders, operation, opts.bridgeSlippage), resultOrders,
assetFillAmount, assetFillAmount,
gasPrice, gasPrice,
operation, 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( const worstCaseQuoteInfo = await this._calculateQuoteInfoAsync(
_.reverse(resultOrders.slice()), resultOrders,
assetFillAmount, assetFillAmount,
gasPrice, gasPrice,
operation, operation,
true,
); );
const quoteBase = { const quoteBase = {
takerAssetData, takerAssetData,
makerAssetData, makerAssetData,
orders: resultOrders, // Remove fill metadata.
orders: resultOrders.map(o => _.omit(o, 'fill')) as SignedOrderWithFillableAmounts[],
bestCaseQuoteInfo, bestCaseQuoteInfo,
worstCaseQuoteInfo, worstCaseQuoteInfo,
gasPrice, gasPrice,
@ -135,31 +203,39 @@ export class SwapQuoteCalculator {
// tslint:disable-next-line: prefer-function-over-method // tslint:disable-next-line: prefer-function-over-method
private async _calculateQuoteInfoAsync( private async _calculateQuoteInfoAsync(
prunedOrders: SignedOrderWithFillableAmounts[], orders: OptimizedMarketOrder[],
assetFillAmount: BigNumber, assetFillAmount: BigNumber,
gasPrice: BigNumber, gasPrice: BigNumber,
operation: MarketOperation, operation: MarketOperation,
worstCase: boolean = false,
): Promise<SwapQuoteInfo> { ): Promise<SwapQuoteInfo> {
if (operation === MarketOperation.Buy) { if (operation === MarketOperation.Buy) {
return this._calculateMarketBuyQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice); return this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
} else { } else {
return this._calculateMarketSellQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice); return this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
} }
} }
private async _calculateMarketSellQuoteInfoAsync( private async _calculateMarketSellQuoteInfoAsync(
prunedOrders: SignedOrderWithFillableAmounts[], orders: OptimizedMarketOrder[],
takerAssetSellAmount: BigNumber, takerAssetSellAmount: BigNumber,
gasPrice: BigNumber, gasPrice: BigNumber,
worstCase: boolean = false,
): Promise<SwapQuoteInfo> { ): Promise<SwapQuoteInfo> {
const result = prunedOrders.reduce( let totalMakerAssetAmount = constants.ZERO_AMOUNT;
(acc, order) => { let totalTakerAssetAmount = constants.ZERO_AMOUNT;
const { let totalFeeTakerAssetAmount = constants.ZERO_AMOUNT;
totalMakerAssetAmount, let remainingTakerAssetFillAmount = takerAssetSellAmount;
totalTakerAssetAmount, const filledOrders = [] as OptimizedMarketOrder[];
totalFeeTakerAssetAmount, const _orders = !worstCase ? orders : orders.slice().reverse();
remainingTakerAssetFillAmount, for (const order of _orders) {
} = acc; 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( const adjustedFillableMakerAssetAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterFees(
order, order,
); );
@ -170,99 +246,155 @@ export class SwapQuoteCalculator {
remainingTakerAssetFillAmount, remainingTakerAssetFillAmount,
adjustedFillableTakerAssetAmount, adjustedFillableTakerAssetAmount,
); );
const { takerAssetAmount, feeTakerAssetAmount } = getTakerAssetAmountBreakDown( const takerAssetAmountBreakDown = getTakerAssetAmountBreakDown(order, takerAssetAmountWithFees);
order, takerAssetAmount = takerAssetAmountBreakDown.takerAssetAmount;
takerAssetAmountWithFees, feeTakerAssetAmount = takerAssetAmountBreakDown.feeTakerAssetAmount;
); makerAssetAmount = takerAssetAmountWithFees
const makerAssetAmount = takerAssetAmountWithFees
.div(adjustedFillableTakerAssetAmount) .div(adjustedFillableTakerAssetAmount)
.multipliedBy(adjustedFillableMakerAssetAmount) .times(adjustedFillableMakerAssetAmount)
.integerValue(BigNumber.ROUND_DOWN); .integerValue(BigNumber.ROUND_DOWN);
return { } else {
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerAssetAmount), // This is a collapsed bridge order.
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount), // Because collapsed bridge orders actually fill at different rates,
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount), // we can iterate over the uncollapsed fills to get the actual
remainingTakerAssetFillAmount: BigNumber.max( // asset amounts transfered.
constants.ZERO_AMOUNT, // We can also assume there are no fees and the order is not
remainingTakerAssetFillAmount.minus(takerAssetAmountWithFees), // partially filled.
),
}; // Infer the bridge slippage from the difference between the fill
}, // size and the actual order asset amounts.
{ const makerAssetBridgeSlippage = !worstCase
totalMakerAssetAmount: constants.ZERO_AMOUNT, ? constants.ONE_AMOUNT
totalTakerAssetAmount: constants.ZERO_AMOUNT, : order.makerAssetAmount.div(order.fill.totalMakerAssetAmount);
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT, const takerAssetBridgeSlippage = !worstCase
remainingTakerAssetFillAmount: takerAssetSellAmount, ? 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( const protocolFeeInWeiAmount = await this._protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(
prunedOrders, filledOrders,
gasPrice, gasPrice,
); );
return { return {
feeTakerAssetAmount: result.totalFeeTakerAssetAmount, feeTakerAssetAmount: totalFeeTakerAssetAmount,
takerAssetAmount: result.totalTakerAssetAmount, takerAssetAmount: totalTakerAssetAmount,
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount), totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
makerAssetAmount: result.totalMakerAssetAmount, makerAssetAmount: totalMakerAssetAmount,
protocolFeeInWeiAmount, protocolFeeInWeiAmount,
}; };
} }
private async _calculateMarketBuyQuoteInfoAsync( private async _calculateMarketBuyQuoteInfoAsync(
prunedOrders: SignedOrderWithFillableAmounts[], orders: OptimizedMarketOrder[],
makerAssetBuyAmount: BigNumber, makerAssetBuyAmount: BigNumber,
gasPrice: BigNumber, gasPrice: BigNumber,
worstCase: boolean = false,
): Promise<SwapQuoteInfo> { ): Promise<SwapQuoteInfo> {
const result = prunedOrders.reduce( let totalMakerAssetAmount = constants.ZERO_AMOUNT;
(acc, order) => { let totalTakerAssetAmount = constants.ZERO_AMOUNT;
const { let totalFeeTakerAssetAmount = constants.ZERO_AMOUNT;
totalMakerAssetAmount, let remainingMakerAssetFillAmount = makerAssetBuyAmount;
totalTakerAssetAmount, const filledOrders = [] as OptimizedMarketOrder[];
totalFeeTakerAssetAmount, const _orders = !worstCase ? orders : orders.slice().reverse();
remainingMakerAssetFillAmount, for (const order of _orders) {
} = acc; 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( const adjustedFillableMakerAssetAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterFees(
order, order,
); );
const adjustedFillableTakerAssetAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterFees( const adjustedFillableTakerAssetAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterFees(
order, order,
); );
const makerFillAmount = BigNumber.min(remainingMakerAssetFillAmount, adjustedFillableMakerAssetAmount); makerAssetAmount = BigNumber.min(remainingMakerAssetFillAmount, adjustedFillableMakerAssetAmount);
const takerAssetAmountWithFees = makerFillAmount const takerAssetAmountWithFees = makerAssetAmount
.div(adjustedFillableMakerAssetAmount) .div(adjustedFillableMakerAssetAmount)
.multipliedBy(adjustedFillableTakerAssetAmount) .multipliedBy(adjustedFillableTakerAssetAmount)
.integerValue(BigNumber.ROUND_UP); .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( // Infer the bridge slippage from the difference between the fill
order, // size and the actual order asset amounts.
takerAssetAmountWithFees, const makerAssetBridgeSlippage = !worstCase
); ? constants.ONE_AMOUNT
return { : order.makerAssetAmount.div(order.fill.totalMakerAssetAmount);
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerFillAmount), const takerAssetBridgeSlippage = !worstCase
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount), ? constants.ONE_AMOUNT
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount), : order.takerAssetAmount.div(order.fill.totalTakerAssetAmount);
remainingMakerAssetFillAmount: BigNumber.max( // Consecutively fill the subfills in this order.
constants.ZERO_AMOUNT, const subFills = !worstCase ? order.fill.subFills : order.fill.subFills.slice().reverse();
remainingMakerAssetFillAmount.minus(makerFillAmount), for (const subFill of subFills) {
), if (remainingMakerAssetFillAmount.minus(makerAssetAmount).lte(0)) {
}; break;
}, }
{ const partialTakerAssetAmount = subFill.takerAssetAmount.times(takerAssetBridgeSlippage);
totalMakerAssetAmount: constants.ZERO_AMOUNT, const partialMakerAssetAmount = subFill.makerAssetAmount.times(makerAssetBridgeSlippage);
totalTakerAssetAmount: constants.ZERO_AMOUNT, const partialMakerAssetFillAmount = BigNumber.min(
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT, partialMakerAssetAmount,
remainingMakerAssetFillAmount: makerAssetBuyAmount, 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( const protocolFeeInWeiAmount = await this._protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(
prunedOrders, filledOrders,
gasPrice, gasPrice,
); );
return { return {
feeTakerAssetAmount: result.totalFeeTakerAssetAmount, feeTakerAssetAmount: totalFeeTakerAssetAmount,
takerAssetAmount: result.totalTakerAssetAmount, takerAssetAmount: totalTakerAssetAmount,
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount), totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
makerAssetAmount: result.totalMakerAssetAmount, makerAssetAmount: totalMakerAssetAmount,
protocolFeeInWeiAmount, protocolFeeInWeiAmount,
}; };
} }
@ -303,35 +435,3 @@ function getTakerAssetAmountBreakDown(
takerAssetAmount: takerAssetAmountWithFees, 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;
}

View File

@ -23,7 +23,7 @@ describe('#calculateLiquidity', () => {
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity( const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
prunedSignedOrders, prunedSignedOrders,
); );
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10)); expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(11));
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(9)); expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(9));
}); });
it('should provide correct liquidity result with orders with takerFees in takerAsset', () => { it('should provide correct liquidity result with orders with takerFees in takerAsset', () => {
@ -31,7 +31,7 @@ describe('#calculateLiquidity', () => {
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity( const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
prunedSignedOrders, prunedSignedOrders,
); );
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10)); expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(11));
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(15)); expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(15));
}); });
it('should provide correct liquidity result with orders with takerFees in makerAsset', () => { it('should provide correct liquidity result with orders with takerFees in makerAsset', () => {
@ -51,7 +51,7 @@ describe('#calculateLiquidity', () => {
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity( const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
prunedSignedOrders, prunedSignedOrders,
); );
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(25)); expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(27));
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(33)); expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(33));
}); });
}); });

View File

@ -123,7 +123,7 @@ describe('ExchangeSwapQuoteConsumer', () => {
}; };
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams); 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( expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
erc20TakerTokenContract, erc20TakerTokenContract,
makerAddress, makerAddress,

View File

@ -126,7 +126,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
}; };
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams); 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( expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory(
erc20TokenContract, erc20TokenContract,
makerAddress, makerAddress,

View File

@ -306,7 +306,7 @@ describe('MarketOperationUtils tests', () => {
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]), _.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
); );
const DEFAULT_SAMPLER = createSamplerFromSellRates(SOURCE_RATES); 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( const defaultMarketOperationUtils = new MarketOperationUtils(
DEFAULT_SAMPLER, DEFAULT_SAMPLER,
contractAddresses, contractAddresses,
@ -552,7 +552,7 @@ describe('MarketOperationUtils tests', () => {
_.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]), _.times(3, () => SOURCE_RATES[ERC20BridgeSource.Native][0]),
); );
const DEFAULT_SAMPLER = createSamplerFromBuyRates(SOURCE_RATES); 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( const defaultMarketOperationUtils = new MarketOperationUtils(
DEFAULT_SAMPLER, DEFAULT_SAMPLER,
contractAddresses, contractAddresses,

View File

@ -65,6 +65,9 @@ const createSamplerFromSignedOrdersWithFillableAmounts = (
return sampler; 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:max-file-line-count
// tslint:disable:custom-no-magic-numbers // tslint:disable:custom-no-magic-numbers
describe('swapQuoteCalculator', () => { describe('swapQuoteCalculator', () => {
@ -272,7 +275,7 @@ describe('swapQuoteCalculator', () => {
}); });
}); });
it('calculates a correct swapQuote with slippage (feeless orders)', async () => { it('calculates a correct swapQuote with slippage (feeless orders)', async () => {
const assetSellAmount = baseUnitAmount(1); const assetSellAmount = baseUnitAmount(4);
const slippagePercentage = 0.2; const slippagePercentage = 0.2;
const sampler = createSamplerFromSignedOrdersWithFillableAmounts( const sampler = createSamplerFromSignedOrdersWithFillableAmounts(
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS, testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS,
@ -289,10 +292,10 @@ describe('swapQuoteCalculator', () => {
GAS_PRICE, GAS_PRICE,
CALCULATE_SWAP_QUOTE_OPTS, CALCULATE_SWAP_QUOTE_OPTS,
); );
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ expect(swapQuote.orders).to.deep.equal([
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[0], testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[0],
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[2],
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[1], testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[1],
]); ]);
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount); expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
@ -301,15 +304,15 @@ describe('swapQuoteCalculator', () => {
feeTakerAssetAmount: baseUnitAmount(0), feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: assetSellAmount, takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(6), makerAssetAmount: baseUnitAmount(9),
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0), feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: assetSellAmount, takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.4), makerAssetAmount: baseUnitAmount(1.6),
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
}); });
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', async () => { it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', async () => {
@ -372,7 +375,7 @@ describe('swapQuoteCalculator', () => {
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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[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); expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
// test if rates are correct // test if rates are correct
@ -381,14 +384,14 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2.25)), takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2.25)),
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(4.5), makerAssetAmount: baseUnitAmount(4.5),
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0.5), feeTakerAssetAmount: baseUnitAmount(1.2),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(0.5)), takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.2)),
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(1), makerAssetAmount: baseUnitAmount(1.8),
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
}); });
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', async () => { it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', async () => {
@ -411,23 +414,24 @@ describe('swapQuoteCalculator', () => {
); );
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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); expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
// test if rates are correct // test if rates are correct
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({ expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2), feeTakerAssetAmount: baseUnitAmount(1.5).minus(1),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)), takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.5)).plus(1),
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8), makerAssetAmount: baseUnitAmount(4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2), feeTakerAssetAmount: baseUnitAmount(1.5).minus(1),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)), takerAssetAmount: assetSellAmount.minus(baseUnitAmount(1.5)).plus(1),
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8), makerAssetAmount: baseUnitAmount(4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
}); });
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', async () => { it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', async () => {
@ -451,24 +455,24 @@ describe('swapQuoteCalculator', () => {
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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[1],
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[2],
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0], testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_MAKER_ASSET[0],
]); ]);
expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount); expect(swapQuote.takerAssetFillAmount).to.bignumber.equal(assetSellAmount);
// test if rates are correct // 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({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2), feeTakerAssetAmount: baseUnitAmount(2),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)), takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
totalTakerAssetAmount: assetSellAmount, totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8), makerAssetAmount: baseUnitAmount(0.8),
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 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),
}); });
}); });
}); });
@ -678,7 +682,7 @@ describe('swapQuoteCalculator', () => {
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ expect(swapQuote.orders).to.deep.equal([
testOrders.SIGNED_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS[0], 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); expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
@ -692,12 +696,16 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount, takerAssetAmount,
totalTakerAssetAmount: takerAssetAmount, totalTakerAssetAmount: takerAssetAmount,
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0), feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: baseUnitAmount(5.5), takerAssetAmount: baseUnitAmount(20)
totalTakerAssetAmount: baseUnitAmount(5.5), .div(6)
.integerValue(BigNumber.ROUND_UP),
totalTakerAssetAmount: baseUnitAmount(20)
.div(6)
.integerValue(BigNumber.ROUND_UP),
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
@ -766,7 +774,7 @@ describe('swapQuoteCalculator', () => {
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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[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); expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
// test if rates are correct // test if rates are correct
@ -775,12 +783,16 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: fiveSixthEthInWei, takerAssetAmount: fiveSixthEthInWei,
totalTakerAssetAmount: baseUnitAmount(2.5).plus(fiveSixthEthInWei), totalTakerAssetAmount: baseUnitAmount(2.5).plus(fiveSixthEthInWei),
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.5), feeTakerAssetAmount: baseUnitAmount(3),
takerAssetAmount: baseUnitAmount(5.5), takerAssetAmount: baseUnitAmount(10)
totalTakerAssetAmount: baseUnitAmount(8), .div(3)
.integerValue(BigNumber.ROUND_UP), // 3.3333...
totalTakerAssetAmount: baseUnitAmount(19)
.div(3)
.integerValue(BigNumber.ROUND_UP), // 6.3333...
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
@ -805,21 +817,33 @@ describe('swapQuoteCalculator', () => {
); );
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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); expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
// test if rates are correct // test if rates are correct
expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({ expect(swapQuote.bestCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.5), feeTakerAssetAmount: baseUnitAmount(0.5)
takerAssetAmount: baseUnitAmount(2.5), .div(3)
totalTakerAssetAmount: baseUnitAmount(5), .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, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(15, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.5), feeTakerAssetAmount: baseUnitAmount(0.5)
takerAssetAmount: baseUnitAmount(2.5), .div(3)
totalTakerAssetAmount: baseUnitAmount(5), .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, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(15, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
@ -845,14 +869,14 @@ describe('swapQuoteCalculator', () => {
// test if orders are correct // test if orders are correct
expect(swapQuote.orders).to.deep.equal([ 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[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); expect(swapQuote.makerAssetFillAmount).to.bignumber.equal(assetBuyAmount);
// test if rates are correct // test if rates are correct
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({ expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.75), feeTakerAssetAmount: baseUnitAmount(1.25).minus(1),
takerAssetAmount: baseUnitAmount(2.75), takerAssetAmount: baseUnitAmount(2.25).plus(1),
totalTakerAssetAmount: baseUnitAmount(5.5), totalTakerAssetAmount: baseUnitAmount(3.5),
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(30, 4),
}); });
@ -879,7 +903,7 @@ describe('swapQuoteCalculator', () => {
.multipliedBy(0.1) .multipliedBy(0.1)
.integerValue(BigNumber.ROUND_CEIL), .integerValue(BigNumber.ROUND_CEIL),
makerAssetAmount: assetBuyAmount, makerAssetAmount: assetBuyAmount,
protocolFeeInWeiAmount: baseUnitAmount(30, 4), protocolFeeInWeiAmount: baseUnitAmount(15, 4),
}); });
}); });
}); });

View File

@ -119,7 +119,7 @@ describe('swapQuoteConsumerUtils', () => {
}; };
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams); 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); forwarderOrderFactory = new OrderFactory(privateKey, defaultForwarderOrderParams);
swapQuoteConsumer = new SwapQuoteConsumer(provider, { swapQuoteConsumer = new SwapQuoteConsumer(provider, {

View File

@ -51,7 +51,7 @@ const PARTIAL_ORDERS_WITH_FILLABLE_AMOUNTS_FEELESS: Array<Partial<SignedOrderWit
takerAssetAmount: baseUnitAmount(6), takerAssetAmount: baseUnitAmount(6),
makerAssetAmount: baseUnitAmount(6), makerAssetAmount: baseUnitAmount(6),
fillableTakerAssetAmount: baseUnitAmount(3), fillableTakerAssetAmount: baseUnitAmount(3),
fillableMakerAssetAmount: baseUnitAmount(2), fillableMakerAssetAmount: baseUnitAmount(3),
}, },
...PARTIAL_ORDER, ...PARTIAL_ORDER,
}, },
@ -86,7 +86,7 @@ const PARTIAL_ORDERS_WITH_FILLABLE_AMOUNTS_FEE_IN_TAKER_ASSET: Array<Partial<Sig
makerAssetAmount: baseUnitAmount(6), makerAssetAmount: baseUnitAmount(6),
takerFee: baseUnitAmount(4), takerFee: baseUnitAmount(4),
fillableTakerAssetAmount: baseUnitAmount(3), fillableTakerAssetAmount: baseUnitAmount(3),
fillableMakerAssetAmount: baseUnitAmount(2), fillableMakerAssetAmount: baseUnitAmount(3),
fillableTakerFeeAmount: baseUnitAmount(2), fillableTakerFeeAmount: baseUnitAmount(2),
}, },
...PARTIAL_ORDER_FEE_IN_TAKER_ASSET, ...PARTIAL_ORDER_FEE_IN_TAKER_ASSET,

View File

@ -9,6 +9,10 @@
{ {
"note": "Update `DevUtils` mainnet, Ropsten, Rinkeby, Kovan addresses", "note": "Update `DevUtils` mainnet, Ropsten, Rinkeby, Kovan addresses",
"pr": 2436 "pr": 2436
},
{
"note": "Update `ERC20BridgeSampler` address on kovan and mainnet.",
"pr": 2427
} }
] ]
}, },

View File

@ -22,7 +22,7 @@
"devUtils": "0x5f53f2aa72cb3a9371bf3c58e8fb3a313478b2f4", "devUtils": "0x5f53f2aa72cb3a9371bf3c58e8fb3a313478b2f4",
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0", "erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f", "uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
"erc20BridgeSampler": "0x1b402fdb5ee87f989c11e3963557e89cc313b6c0", "erc20BridgeSampler": "0x25840bf3582cb9e5acabbf45148b3092ac3f6b56",
"kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62", "kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62",
"eth2DaiBridge": "0xe97ea901d034ba2e018155264f77c417ce7717f9", "eth2DaiBridge": "0xe97ea901d034ba2e018155264f77c417ce7717f9",
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438", "chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",
@ -110,7 +110,7 @@
"erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64", "erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64",
"uniswapBridge": "0x0000000000000000000000000000000000000000", "uniswapBridge": "0x0000000000000000000000000000000000000000",
"eth2DaiBridge": "0x0000000000000000000000000000000000000000", "eth2DaiBridge": "0x0000000000000000000000000000000000000000",
"erc20BridgeSampler": "0x551f0e213dcb71f676558d8b0ab559d1cdd103f2", "erc20BridgeSampler": "0x8c9f255253bcf2c9539c887fee4d71c69fd035b9",
"kyberBridge": "0x0000000000000000000000000000000000000000", "kyberBridge": "0x0000000000000000000000000000000000000000",
"chaiBridge": "0x0000000000000000000000000000000000000000", "chaiBridge": "0x0000000000000000000000000000000000000000",
"dydxBridge": "0x0000000000000000000000000000000000000000" "dydxBridge": "0x0000000000000000000000000000000000000000"

View File

@ -9,6 +9,10 @@
{ {
"note": "Update all artifacts.", "note": "Update all artifacts.",
"pr": 2432 "pr": 2432
},
{
"note": "Update `IERC20BridgeSampler artifact",
"pr": 2427
} }
] ]
}, },

View File

@ -71,6 +71,94 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "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, "constant": true,
"inputs": [ "inputs": [
@ -196,6 +284,26 @@
}, },
"return": "orderFillableTakerAssetAmounts How much taker asset can be filled by each order in `orders`." "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[])": { "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.", "details": "Query native orders and sample buy quotes on multiple DEXes at once.",
"params": { "params": {

View File

@ -13,6 +13,10 @@
{ {
"note": "Regenerate wrappers to catch empty reverts on live networks.", "note": "Regenerate wrappers to catch empty reverts on live networks.",
"pr": 2433 "pr": 2433
},
{
"note": "Update `IERC20BridgeSampler`",
"pr": 2427
} }
] ]
}, },

View File

@ -18,7 +18,7 @@
"rebuild": "yarn wrappers:clean && yarn wrappers:generate && yarn wrappers:prettier && yarn build", "rebuild": "yarn wrappers:clean && yarn wrappers:generate && yarn wrappers:prettier && yarn build",
"build:ci": "yarn build", "build:ci": "yarn build",
"lint": "tslint --format stylish --project . --exclude **/lib/**/*", "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", "prettier": "prettier --write **/* --config ../../.prettierrc",
"clean": "shx rm -rf lib generated_docs", "clean": "shx rm -rf lib generated_docs",
"docs_test": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --out generated_docs ./src/generated-wrappers/*", "docs_test": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --out generated_docs ./src/generated-wrappers/*",

View File

@ -281,6 +281,204 @@ export class IERC20BridgeSamplerContract extends BaseContract {
stateMutability: 'view', stateMutability: 'view',
type: 'function', 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, constant: true,
inputs: [ 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. * Query native orders and sample buy quotes on multiple DEXes at once.
* @param orders Native orders to query. * @param orders Native orders to query.

View File

@ -1,4 +1,13 @@
[ [
{
"version": "2.1.0",
"changes": [
{
"note": "Added `getBatchOrdersAsync` for fetching batches of orders",
"pr": 2427
}
]
},
{ {
"timestamp": 1578272714, "timestamp": 1578272714,
"version": "2.0.1", "version": "2.0.1",

View File

@ -25,6 +25,19 @@ export class OrderStore {
} }
return orderSet; 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> { public async updateAsync(addedRemoved: AddedRemovedOrders): Promise<void> {
const { added, removed, assetPairKey } = addedRemoved; const { added, removed, assetPairKey } = addedRemoved;
const orders = await this.getOrderSetForAssetPairAsync(assetPairKey); const orders = await this.getOrderSetForAssetPairAsync(assetPairKey);

View File

@ -84,6 +84,26 @@ export class Orderbook {
o => o.order.makerAssetData === makerAssetData && o.order.takerAssetData === takerAssetData, 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. * Returns all of the Available Asset Pairs for the provided Order Provider.
*/ */

View File

@ -117,17 +117,11 @@ class CleanCommandExtension(clean):
) )
): ):
try: try:
remove( print(f"Removing {contract}...", end="")
path.join( remove(contract)
"src", print("done")
"zero_ex",
"contract_wrappers",
contract,
"__init__.py",
)
)
except FileNotFoundError: except FileNotFoundError:
pass print("file not found")
class TestPublishCommand(distutils.command.build_py.build_py): class TestPublishCommand(distutils.command.build_py.build_py):