@0x/asset-swapper: Add gasSchedule option to SwapQuoter.

`@0x/asset-swapper`: Rename `fees` `SwapQuoter` option to `feeSchedule`.
This commit is contained in:
Lawrence Forman 2020-03-09 22:22:57 -04:00
parent cc12ad8d86
commit 05bf55dca8
9 changed files with 52 additions and 25 deletions

View File

@ -169,6 +169,7 @@ export interface MarketBuySwapQuote extends SwapQuoteBase {
* totalTakerAssetAmount: The total amount of takerAsset required to complete the swap (filling orders, and paying takerFees).
* makerAssetAmount: The amount of makerAsset that will be acquired through the swap.
* protocolFeeInWeiAmount: The amount of ETH to pay (in WEI) as protocol fee to perform the swap for desired asset.
* gas: Amount of estimated gas needed to fill the quote.
*/
export interface SwapQuoteInfo {
feeTakerAssetAmount: BigNumber;
@ -176,6 +177,7 @@ export interface SwapQuoteInfo {
totalTakerAssetAmount: BigNumber;
makerAssetAmount: BigNumber;
protocolFeeInWeiAmount: BigNumber;
gas: number;
}
/**

View File

@ -82,6 +82,7 @@ export const assert = {
);
},
isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void {
sharedAssert.isNumber(`${variableName}.gas`, swapQuoteInfo.gas);
sharedAssert.isBigNumber(`${variableName}.feeTakerAssetAmount`, swapQuoteInfo.feeTakerAssetAmount);
sharedAssert.isBigNumber(`${variableName}.totalTakerAssetAmount`, swapQuoteInfo.totalTakerAssetAmount);
sharedAssert.isBigNumber(`${variableName}.takerAssetAmount`, swapQuoteInfo.takerAssetAmount);

View File

@ -30,7 +30,8 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
bridgeSlippage: 0.005,
numSamples: 20,
sampleDistributionBase: 1.05,
fees: {},
feeSchedule: {},
gasSchedule: {},
allowFallback: true,
};

View File

@ -26,18 +26,18 @@ export function createFillPaths(opts: {
targetInput?: BigNumber;
ethToOutputRate?: BigNumber;
excludedSources?: ERC20BridgeSource[];
fees?: { [source: string]: BigNumber };
feeSchedule?: { [source: string]: BigNumber };
}): Fill[][] {
const { side } = opts;
const excludedSources = opts.excludedSources || [];
const fees = opts.fees || {};
const feeSchedule = opts.feeSchedule || {};
const orders = opts.orders || [];
const dexQuotes = opts.dexQuotes || [];
const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT;
// Create native fill paths.
const nativePath = nativeOrdersToPath(side, orders, ethToOutputRate, fees);
const nativePath = nativeOrdersToPath(side, orders, ethToOutputRate, feeSchedule);
// Create DEX fill paths.
const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, fees);
const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, feeSchedule);
return filterPaths([...dexPaths, nativePath].map(p => clipPathToInput(p, opts.targetInput)), excludedSources);
}

View File

@ -98,7 +98,7 @@ export class MarketOperationUtils {
ethToOutputRate: ethToMakerAssetRate,
bridgeSlippage: _opts.bridgeSlippage,
excludedSources: _opts.excludedSources,
fees: _opts.fees,
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
});
}
@ -170,7 +170,7 @@ export class MarketOperationUtils {
ethToOutputRate: ethToTakerAssetRate,
bridgeSlippage: _opts.bridgeSlippage,
excludedSources: _opts.excludedSources,
fees: _opts.fees,
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
});
}
@ -242,7 +242,7 @@ export class MarketOperationUtils {
ethToOutputRate: ethToTakerAssetRate,
bridgeSlippage: _opts.bridgeSlippage,
excludedSources: _opts.excludedSources,
fees: _opts.fees,
feeSchedule: _opts.feeSchedule,
allowFallback: _opts.allowFallback,
});
});
@ -260,7 +260,7 @@ export class MarketOperationUtils {
ethToOutputRate?: BigNumber;
bridgeSlippage?: number;
excludedSources?: ERC20BridgeSource[];
fees?: { [source: string]: BigNumber };
feeSchedule?: { [source: string]: BigNumber };
allowFallback?: boolean;
liquidityProviderAddress?: string;
}): OptimizedMarketOrder[] {
@ -274,7 +274,7 @@ export class MarketOperationUtils {
targetInput: inputAmount,
ethToOutputRate: opts.ethToOutputRate,
excludedSources: opts.excludedSources,
fees: opts.fees,
feeSchedule: opts.feeSchedule,
});
// Find the optimal path.
const optimalPath = findOptimalPath(side, paths, inputAmount, opts.runLimit);

View File

@ -167,7 +167,11 @@ export interface GetMarketOrdersOpts {
/**
* Fees for each liquidity source, expressed in gas.
*/
fees: { [source: string]: BigNumber };
feeSchedule: { [source: string]: BigNumber };
/**
* Estimated gas consumed by each liquidity source.
*/
gasSchedule: { [source: string]: number };
/**
* Whether to pad the quote with a redundant fallback quote using different
* sources.

View File

@ -106,6 +106,7 @@ export class SwapQuoteCalculator {
operation,
assetFillAmounts[i],
gasPrice,
opts.gasSchedule,
);
} else {
return undefined;
@ -133,7 +134,7 @@ export class SwapQuoteCalculator {
// Scale fees by gas price.
const _opts = {
...opts,
fees: _.mapValues(opts.fees, (v, k) => v.times(gasPrice)),
fees: _.mapValues(opts.feeSchedule, v => v.times(gasPrice)),
};
const firstOrderMakerAssetData = !!prunedOrders[0]
@ -169,6 +170,7 @@ export class SwapQuoteCalculator {
operation,
assetFillAmount,
gasPrice,
opts.gasSchedule,
);
}
private async _createSwapQuoteAsync(
@ -178,17 +180,20 @@ export class SwapQuoteCalculator {
operation: MarketOperation,
assetFillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: { [source: string]: number },
): Promise<SwapQuote> {
const bestCaseQuoteInfo = await this._calculateQuoteInfoAsync(
resultOrders,
assetFillAmount,
gasPrice,
gasSchedule,
operation,
);
const worstCaseQuoteInfo = await this._calculateQuoteInfoAsync(
resultOrders,
assetFillAmount,
gasPrice,
gasSchedule,
operation,
true,
);
@ -226,14 +231,16 @@ export class SwapQuoteCalculator {
orders: OptimizedMarketOrder[],
assetFillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: { [source: string]: number },
operation: MarketOperation,
worstCase: boolean = false,
): Promise<SwapQuoteInfo> {
if (operation === MarketOperation.Buy) {
return this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
} else {
return this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase);
}
return {
...(operation === MarketOperation.Buy
? await this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase)
: await this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase)),
gas: getGasUsedByOrders(orders, gasSchedule),
};
}
private async _calculateMarketSellQuoteInfoAsync(
@ -327,6 +334,7 @@ export class SwapQuoteCalculator {
totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
makerAssetAmount: totalMakerAssetAmount,
protocolFeeInWeiAmount,
gas: 0,
};
}
@ -416,6 +424,7 @@ export class SwapQuoteCalculator {
totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount),
makerAssetAmount: totalMakerAssetAmount,
protocolFeeInWeiAmount,
gas: 0,
};
}
@ -485,3 +494,12 @@ function getTakerAssetAmountBreakDown(
takerAssetAmount: takerAssetAmountWithFees,
};
}
function getGasUsedByOrders(orders: OptimizedMarketOrder[], gasSchedule: { [source: string]: number }): number {
let totalUsage = 0;
for (const order of orders) {
totalUsage += gasSchedule[order.fill.source] || 0;
}
return totalUsage;
}
// tslint:disable: max-file-line-count

View File

@ -486,7 +486,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
};
const fees = {
const feeSchedule = {
[ERC20BridgeSource.Native]: FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
@ -498,7 +498,7 @@ describe('MarketOperationUtils tests', () => {
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
FILL_AMOUNT,
{ ...DEFAULT_OPTS, numSamples: 4, fees },
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
);
const orderSources = improvedOrders.map(o => o.fill.source);
const expectedSources = [
@ -521,7 +521,7 @@ describe('MarketOperationUtils tests', () => {
// Effectively [0.8, ~0.5, ~0, ~0]
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
};
const fees = {
const feeSchedule = {
[ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
@ -533,7 +533,7 @@ describe('MarketOperationUtils tests', () => {
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
FILL_AMOUNT,
{ ...DEFAULT_OPTS, numSamples: 4, fees },
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
);
const orderSources = improvedOrders.map(o => o.fill.source);
const expectedSources = [
@ -828,7 +828,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
};
const fees = {
const feeSchedule = {
[ERC20BridgeSource.Native]: FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
@ -840,7 +840,7 @@ describe('MarketOperationUtils tests', () => {
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
FILL_AMOUNT,
{ ...DEFAULT_OPTS, numSamples: 4, fees },
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
);
const orderSources = improvedOrders.map(o => o.fill.source);
const expectedSources = [
@ -862,7 +862,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
[ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1],
};
const fees = {
const feeSchedule = {
[ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
@ -874,7 +874,7 @@ describe('MarketOperationUtils tests', () => {
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
FILL_AMOUNT,
{ ...DEFAULT_OPTS, numSamples: 4, fees },
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
);
const orderSources = improvedOrders.map(o => o.fill.source);
const expectedSources = [

View File

@ -25,6 +25,7 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
takerAssetAmount: totalTakerAssetAmount,
totalTakerAssetAmount,
protocolFeeInWeiAmount: await protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(orders, gasPrice),
gas: 200e3,
};
const breakdown = {