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 **
// Scale fees by gas price.
const cloneOpts = _.omit(opts, 'gasPrice') as GetMarketOrdersOpts;
const calcOpts: GetMarketOrdersOpts = {
...cloneOpts,
...opts,
gasPrice,
exchangeProxyOverhead: opts.exchangeProxyOverhead,
};

View File

@@ -227,13 +227,7 @@ export class MarketOperationUtils {
makerTokenDecimals: makerTokenDecimals,
takerTokenDecimals: takerTokenDecimals,
gasPrice: opts.gasPrice,
quotes: sampleLegs.map((tokenPath, i) => ({
tokenPath,
inputToken: tokenPath[0],
outputToken: tokenPath[tokenPath.length - 1],
nativeOrders: [],
dexQuotes: samples[i],
})).filter(doesRawHopQuotesHaveLiquidity),
quotes: createRawHopQuotesFromSamples(MarketOperation.Sell, sampleLegs, samples),
isRfqSupported,
};
}
@@ -335,6 +329,7 @@ export class MarketOperationUtils {
twoHopPathDetails = twoHopPathDetails
.sort((a, b) => -a.totalPrice.comparedTo(b.totalPrice))
.slice(0, 3);
console.log(twoHopPathDetails.map(p => ({ ...p, legs: p.legs.join(' -> ') })));
if (side === MarketOperation.Buy) {
// Reverse legs and prices and invert prices for buys.
@@ -471,13 +466,7 @@ export class MarketOperationUtils {
makerTokenDecimals: makerTokenDecimals,
takerTokenDecimals: takerTokenDecimals,
gasPrice: opts.gasPrice,
quotes: sampleLegs.map((tokenPath, i) => ({
tokenPath,
inputToken: tokenPath[tokenPath.length - 1],
outputToken: tokenPath[0],
nativeOrders: [],
dexQuotes: samples[i],
})).filter(doesRawHopQuotesHaveLiquidity),
quotes: createRawHopQuotesFromSamples(MarketOperation.Buy, sampleLegs, samples),
isRfqSupported,
};
}
@@ -951,6 +940,7 @@ export class MarketOperationUtils {
let hopInputAmount = inputAmount;
const hops = [];
for (const routeHop of route) {
console.log([routeHop.inputToken, routeHop.outputToken], routeHop.dexQuotes.map(q => q[0].source));
const hop = await this._createOptimizedHopAsync({
side,
slippage,
@@ -995,8 +985,9 @@ export class MarketOperationUtils {
};
for (const route of hopRoutes) {
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)) {
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;
bestHopRouteTotalRate = rate;
}
@@ -1109,7 +1100,6 @@ function injectRfqLiquidity(
quotes.push({
inputToken,
outputToken,
tokenPath: [takerToken, makerToken],
dexQuotes: [],
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 {
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);
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 { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types';
import { DexSample, ERC20BridgeSource } from './types';
import { SamplerServiceRpcClient } from './sampler_service_rpc_client';
const DEFAULT_LIQUIDITY_SAMPLES = 16;

View File

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