use compatible quotes across different hop legs

This commit is contained in:
Lawrence Forman
2022-03-29 11:36:32 -04:00
parent 7967ccfb9d
commit 8389ef31d8
4 changed files with 44 additions and 25 deletions

View File

@@ -319,10 +319,8 @@ export class SwapQuoter {
} }
// ** Prepare options for fetching market side liquidity ** // ** Prepare options for fetching market side liquidity **
// Scale fees by gas price.
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
const calcOpts: GetMarketOrdersOpts = { const calcOpts: GetMarketOrdersOpts = {
...cloneOpts, ...opts,
gasPrice, gasPrice,
exchangeProxyOverhead: opts.exchangeProxyOverhead, exchangeProxyOverhead: opts.exchangeProxyOverhead,
}; };

View File

@@ -227,13 +227,7 @@ export class MarketOperationUtils {
makerTokenDecimals: makerTokenDecimals, makerTokenDecimals: makerTokenDecimals,
takerTokenDecimals: takerTokenDecimals, takerTokenDecimals: takerTokenDecimals,
gasPrice: opts.gasPrice, gasPrice: opts.gasPrice,
quotes: sampleLegs.map((tokenPath, i) => ({ quotes: createRawHopQuotesFromSamples(MarketOperation.Sell, sampleLegs, samples),
tokenPath,
inputToken: tokenPath[0],
outputToken: tokenPath[tokenPath.length - 1],
nativeOrders: [],
dexQuotes: samples[i],
})).filter(doesRawHopQuotesHaveLiquidity),
isRfqSupported, isRfqSupported,
}; };
} }
@@ -335,6 +329,7 @@ export class MarketOperationUtils {
twoHopPathDetails = twoHopPathDetails twoHopPathDetails = twoHopPathDetails
.sort((a, b) => -a.totalPrice.comparedTo(b.totalPrice)) .sort((a, b) => -a.totalPrice.comparedTo(b.totalPrice))
.slice(0, 3); .slice(0, 3);
console.log(twoHopPathDetails.map(p => ({ ...p, legs: p.legs.join(' -> ') })));
if (side === MarketOperation.Buy) { if (side === MarketOperation.Buy) {
// Reverse legs and prices and invert prices for buys. // Reverse legs and prices and invert prices for buys.
@@ -471,13 +466,7 @@ export class MarketOperationUtils {
makerTokenDecimals: makerTokenDecimals, makerTokenDecimals: makerTokenDecimals,
takerTokenDecimals: takerTokenDecimals, takerTokenDecimals: takerTokenDecimals,
gasPrice: opts.gasPrice, gasPrice: opts.gasPrice,
quotes: sampleLegs.map((tokenPath, i) => ({ quotes: createRawHopQuotesFromSamples(MarketOperation.Buy, sampleLegs, samples),
tokenPath,
inputToken: tokenPath[tokenPath.length - 1],
outputToken: tokenPath[0],
nativeOrders: [],
dexQuotes: samples[i],
})).filter(doesRawHopQuotesHaveLiquidity),
isRfqSupported, isRfqSupported,
}; };
} }
@@ -951,6 +940,7 @@ export class MarketOperationUtils {
let hopInputAmount = inputAmount; let hopInputAmount = inputAmount;
const hops = []; const hops = [];
for (const routeHop of route) { for (const routeHop of route) {
console.log([routeHop.inputToken, routeHop.outputToken], routeHop.dexQuotes.map(q => q[0].source));
const hop = await this._createOptimizedHopAsync({ const hop = await this._createOptimizedHopAsync({
side, side,
slippage, slippage,
@@ -995,8 +985,9 @@ export class MarketOperationUtils {
}; };
for (const route of hopRoutes) { for (const route of hopRoutes) {
const rate = getHopRouteOverallAdjustedTakerToMakerRate(route, rateOpts); const rate = getHopRouteOverallAdjustedTakerToMakerRate(route, rateOpts);
console.log(`considering route: <\n\t${route.map(r => `path: ${r.inputToken}->${r.outputToken}, ${r.orders.map(o => o.source)}, output: ${r.outputAmount}`).join(',\n\t')}\n>, overall rate: ${rate}, overall gas cost: ${route.reduce((a,v) => a + v.gasCost, 0)}`);
if (!bestHopRouteTotalRate || rate.gt(bestHopRouteTotalRate)) { if (!bestHopRouteTotalRate || rate.gt(bestHopRouteTotalRate)) {
console.log(`new best route: <\n\t${route.map(r => `path: ${r.inputToken}->${r.outputToken}, ${r.orders.map(o => o.source)}, output: ${r.outputAmount}`).join(',\n\t')}\n>, overall rate: ${rate}, overall gas cost: ${route.reduce((a,v) => a + v.gasCost, 0)}`); console.log(`new best route: <${route.map(r => `path: ${r.inputToken}->${r.outputToken}`)}>`);
bestHopRoute = route; bestHopRoute = route;
bestHopRouteTotalRate = rate; bestHopRouteTotalRate = rate;
} }
@@ -1109,7 +1100,6 @@ function injectRfqLiquidity(
quotes.push({ quotes.push({
inputToken, inputToken,
outputToken, outputToken,
tokenPath: [takerToken, makerToken],
dexQuotes: [], dexQuotes: [],
nativeOrders: fullOrders, nativeOrders: fullOrders,
}); });
@@ -1139,10 +1129,6 @@ function getTerminalTokensFromPaths(paths: Address[][]): Address[] {
]; ];
} }
function doesRawHopQuotesHaveLiquidity(hopQuotes: RawHopQuotes): boolean {
return hopQuotes.dexQuotes.length > 0 || hopQuotes.nativeOrders.length > 0;
}
function isSameTokenPath(a: Address[], b: Address[]): boolean { function isSameTokenPath(a: Address[], b: Address[]): boolean {
return a.length === b.length && a.every((v, idx) => v === b[idx]); return a.length === b.length && a.every((v, idx) => v === b[idx]);
} }
@@ -1151,3 +1137,39 @@ function isSameTokenPath(a: Address[], b: Address[]): boolean {
const [pathTakerToken, pathMakerToken] = getTakerMakerTokenFromTokenPath(tokenPath); const [pathTakerToken, pathMakerToken] = getTakerMakerTokenFromTokenPath(tokenPath);
return pathTakerToken === takerToken && pathMakerToken === makerToken; return pathTakerToken === takerToken && pathMakerToken === makerToken;
} }
function createRawHopQuotesFromSamples(side: MarketOperation, tokenPaths: Address[][], samples: DexSample[][][]): RawHopQuotes[] {
if (tokenPaths.length !== samples.length) {
throw new Error(`Mismatched tokenPaths and samples arrays.`);
}
const filterEmptySamples = (s: DexSample[][]): DexSample[][] => {
return s.map(s => s.filter(ss => !ss.output.isZero())).filter(s => s.length);
};
const hopQuotes = [];
for (let i = 0; i < tokenPaths.length; ++i) {
const tokenPath = tokenPaths[i];
const pathSamples = samples[i];
const [inputToken, outputToken] = [
side === MarketOperation.Sell ? tokenPath[0] : tokenPath[tokenPath.length - 1],
side === MarketOperation.Sell ? tokenPath[tokenPath.length - 1] : tokenPath[0],
];
// See if there's already a hopQuote that has compatible input and output tokens
// and just merge with that one.
// We do this because we sample hidden hops separately but they can technically
// be combined together in the same route.
for (const existing of hopQuotes) {
if (existing.inputToken === inputToken && existing.outputToken === outputToken) {
existing.dexQuotes.push(...filterEmptySamples(pathSamples));
continue;
}
}
hopQuotes.push({
tokenPath,
inputToken,
outputToken,
nativeOrders: [],
dexQuotes: filterEmptySamples(pathSamples),
});
}
return hopQuotes.filter(h => h.dexQuotes.length > 0 || h.nativeOrders.length > 0);
}

View File

@@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils';
import { Address } from '../../types'; import { Address } from '../../types';
import { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types'; import { DexSample, ERC20BridgeSource } from './types';
import { SamplerServiceRpcClient } from './sampler_service_rpc_client'; import { SamplerServiceRpcClient } from './sampler_service_rpc_client';
const DEFAULT_LIQUIDITY_SAMPLES = 16; const DEFAULT_LIQUIDITY_SAMPLES = 16;

View File

@@ -518,7 +518,6 @@ export interface MarketSideLiquidity {
} }
export interface RawHopQuotes { export interface RawHopQuotes {
tokenPath: Address[];
inputToken: Address; inputToken: Address;
outputToken: Address; outputToken: Address;
nativeOrders: NativeOrderWithFillableAmounts[]; nativeOrders: NativeOrderWithFillableAmounts[];