fix gas/overhead evaluation

This commit is contained in:
Lawrence Forman
2022-03-15 19:09:04 -04:00
parent bd0d672012
commit 35c0cafd6b
8 changed files with 107 additions and 32 deletions

View File

@@ -324,7 +324,7 @@ export class SwapQuoter {
const calcOpts: GetMarketOrdersOpts = {
...cloneOpts,
gasPrice,
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
exchangeProxyOverhead: opts.exchangeProxyOverhead,
};
// pass the QuoteRequestor on if rfqt enabled
if (calcOpts.rfqt !== undefined) {

View File

@@ -1,10 +1,10 @@
import { Web3Wrapper } from '@0x/dev-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { MarketOperation } from '../../types';
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
import { COMPARISON_PRICE_DECIMALS } from './constants';
import {
ComparisonPrice,
MarketSideLiquidity,

View File

@@ -189,8 +189,10 @@ export function dexSamplesToFills(
const input = sample.input.minus(prevSample ? prevSample.input : 0);
const output = sample.output.minus(prevSample ? prevSample.output : 0);
const fee = gasPrice.times(sample.gasCost);
let penalty = ZERO_AMOUNT;
if (i === 0) {
console.log(`fee: ${fee}, gasCost: ${sample.gasCost}, gasPrice: ${gasPrice}`);
// Only the first fill in a DEX path incurs a penalty.
penalty = ethToOutputAmount({
input,

View File

@@ -535,16 +535,21 @@ export class MarketOperationUtils {
throw new Error(AggregationError.NoOptimalPath);
}
// TODO: Find the unoptimized best rate to calculate savings from optimizer
const [takerToken, makerToken] = side === MarketOperation.Sell
? [inputToken, outputToken]
: [outputToken, inputToken];
const adjustedRate = getHopRouteOverallAdjustedTakerToMakerRate(bestHopRoute, {
side,
exchangeProxyOverhead: opts.exchangeProxyOverhead,
gasPrice: opts.gasPrice,
inputAmountPerEth: tokenAmountPerEth[inputToken],
outputAmountPerEth: tokenAmountPerEth[outputToken],
});
return {
hops: bestHopRoute,
adjustedRate: getHopRouteOverallRate(bestHopRoute),
// liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
adjustedRate,
marketSideLiquidity,
hops: bestHopRoute,
// liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
takerAmountPerEth: tokenAmountPerEth[takerToken],
makerAmountPerEth: tokenAmountPerEth[makerToken],
};
@@ -755,6 +760,7 @@ export class MarketOperationUtils {
return null;
}
const preFallbackPath = path;
if (doesPathNeedFallback(path)) {
path = await this._addFallbackToPath({
path,
@@ -779,14 +785,15 @@ export class MarketOperationUtils {
outputToken: opts.outputToken,
}).orders;
const completePathSize = preFallbackPath.completeSize();
return {
orders,
inputToken: opts.inputToken,
outputToken: opts.outputToken,
inputAmount: path.size().input,
outputAmount: path.size().output,
adjustedCompleteRate: path.adjustedCompleteMakerToTakerRate(),
sourceFlags: path.sourceFlags,
inputAmount: completePathSize.input,
outputAmount: completePathSize.output,
sourceFlags: preFallbackPath.sourceFlags,
gasCost: preFallbackPath.gasCost(),
};
}
@@ -810,10 +817,12 @@ export class MarketOperationUtils {
inputAmountPerEth: opts.inputAmountPerEth,
gasPrice: opts.gasPrice,
exchangeProxyOverhead: (sourceFlags: bigint) => {
if (!opts.exchangeProxyOverhead) {
if (!opts.exchangeProxyOverhead || opts.isMultiHop) {
// Multihop routes get their overhead factored in after
// path-finding.
return ZERO_AMOUNT;
}
return opts.exchangeProxyOverhead(sourceFlags | (opts.isMultiHop ? SOURCE_FLAGS.MultiHop : BigInt(0)));
return opts.exchangeProxyOverhead(sourceFlags);
},
};
@@ -979,9 +988,18 @@ export class MarketOperationUtils {
// Pick the route with the best rate.
let bestHopRoute;
let bestHopRouteTotalRate;
const rateOpts = {
side,
gasPrice,
exchangeProxyOverhead,
inputAmountPerEth: tokenAmountPerEth[inputToken],
outputAmountPerEth: tokenAmountPerEth[outputToken],
};
for (const route of hopRoutes) {
const rate = getHopRouteOverallRate(route);
console.log(route.map(r => [r.inputToken, r.outputToken]));
const rate = getHopRouteOverallAdjustedTakerToMakerRate(route, rateOpts);
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)}`);
bestHopRoute = route;
bestHopRouteTotalRate = rate;
}
@@ -998,11 +1016,40 @@ function doesPathNeedFallback(path: Path): boolean {
// Compute the overall adjusted rate for a multihop path.
function getHopRouteOverallRate(hops: OptimizedHop[]): BigNumber {
return hops.reduce(
(a, h) => a = a.times(h.adjustedCompleteRate),
new BigNumber(1),
function getHopRouteOverallAdjustedTakerToMakerRate(
hops: OptimizedHop[],
opts: {
side: MarketOperation;
gasPrice: BigNumber;
exchangeProxyOverhead?: ExchangeProxyOverhead;
inputAmountPerEth: BigNumber;
outputAmountPerEth: BigNumber;
}): BigNumber
{
// Note for buys, order of hops will be reversed (maker -> taker), so this still works
// (ie, first hop has the true input amount and last hop has the true output amount).
const inputAmount = hops[0].inputAmount;
const outputAmount = hops[hops.length - 1].outputAmount;
let sourceFlags = hops.reduce((a, h) => a | h.sourceFlags, BigInt(0));
if (hops.length > 1) {
// Multi-hop.
sourceFlags |= SOURCE_FLAGS.MultiHop;
}
const exchangeProxyOverhead = opts.exchangeProxyOverhead || (() => ZERO_AMOUNT);
const totalEthCost = opts.gasPrice.times(
exchangeProxyOverhead(sourceFlags).plus(hops.reduce((a, h) => a + h.gasCost, 0)),
);
console.log(totalEthCost, hops.length, exchangeProxyOverhead(sourceFlags));
const ethCostAsOutputAmount = ethToOutputAmount({
ethAmount: totalEthCost,
input: inputAmount,
output: outputAmount,
inputAmountPerEth: opts.inputAmountPerEth,
outputAmountPerEth: opts.outputAmountPerEth,
});
return opts.side === MarketOperation.Sell
? outputAmount.minus(ethCostAsOutputAmount).div(inputAmount)
: inputAmount.div(outputAmount.plus(ethCostAsOutputAmount));
}
function indicativeRfqQuoteToSignedNativeOrder(iq: V4RFQIndicativeQuote): SignedRfqOrder {

View File

@@ -5,7 +5,7 @@ import { Address, MarketOperation } from '../../types';
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
import { ethToOutputAmount } from './fills';
import { createBridgeOrder, createNativeOptimizedOrder } from './orders';
import { getCompleteRate, getRate } from './rate_utils';
import { getCompleteTakerToMakerRate, getRate } from './rate_utils';
import {
CollapsedGenericBridgeFill,
CollapsedFill,
@@ -136,10 +136,24 @@ export class Path {
return this._size;
}
public completeSize(): PathSize {
const { input, output } = this.size();
if (this.targetInput.eq(0) || input.eq(0) || output.eq(0)) {
return { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
}
if (input.eq(this.targetInput)) {
return { input, output };
}
return {
input: this.targetInput,
output: this.targetInput.times(output.div(input)),
};
}
public adjustedSize(): PathSize {
const { input, output } = this._adjustedSize;
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth, gasPrice } = this.pathPenaltyOpts;
const gasOverhead = exchangeProxyOverhead(this.sourceFlags).times(gasPrice);
const pathPenalty = ethToOutputAmount({
input,
output,
@@ -153,9 +167,9 @@ export class Path {
};
}
public adjustedCompleteMakerToTakerRate(): BigNumber {
public adjustedCompleteTakerToMakerRate(): BigNumber {
const { input, output } = this.adjustedSize();
return getCompleteRate(this.side, input, output, this.targetInput);
return getCompleteTakerToMakerRate(this.side, input, output, this.targetInput);
}
public adjustedRate(): BigNumber {
@@ -163,6 +177,18 @@ export class Path {
return getRate(this.side, input, output);
}
public gasCost(): number {
let lastSource;
let gas = 0;
for (const f of this.fills) {
if (lastSource !== f.source) {
lastSource = f.source;
gas += f.gasCost;
}
}
return gas;
}
/**
* Returns the best possible rate this path can offer, given the fills.
*/
@@ -193,7 +219,7 @@ export class Path {
if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
return input.isGreaterThan(otherInput);
} else {
return this.adjustedCompleteMakerToTakerRate().isGreaterThan(other.adjustedCompleteMakerToTakerRate());
return this.adjustedCompleteTakerToMakerRate().isGreaterThan(other.adjustedCompleteTakerToMakerRate());
}
// if (otherInput.isLessThan(targetInput)) {
// return input.isGreaterThan(otherInput);

View File

@@ -437,7 +437,7 @@ export async function findOptimalPathJSAsync(
// Sort fill arrays by descending adjusted completed rate.
// Remove any paths which cannot impact the optimal path
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts));
console.log(sortedPaths.map(p => ({ source: p.fills[0].source, data: p.fills[0].data, rate: p.adjustedCompleteMakerToTakerRate() })));
console.log(sortedPaths.map(p => ({ source: p.fills[0].source, data: p.fills[0].data, output: p.size().output, adjustedOutput: p.adjustedSize().output, rate: p.adjustedCompleteTakerToMakerRate() })));
if (sortedPaths.length === 0) {
return undefined;
}
@@ -449,7 +449,7 @@ export async function findOptimalPathJSAsync(
await Promise.resolve();
}
const finalPath = optimalPath.isComplete() ? optimalPath : undefined;
console.log(finalPath?.fills.map(f => f.source), finalPath?.adjustedCompleteMakerToTakerRate());
console.log(finalPath?.fills.map(f => f.source), finalPath?.adjustedCompleteTakerToMakerRate());
// tslint:disable-next-line: no-unused-expression
samplerMetrics &&
samplerMetrics.logRouterDetails({
@@ -469,8 +469,8 @@ export function fillsToSortedPaths(
): Path[] {
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
const sortedPaths = paths.sort((a, b) => {
const aRate = a.adjustedCompleteMakerToTakerRate();
const bRate = b.adjustedCompleteMakerToTakerRate();
const aRate = a.adjustedCompleteTakerToMakerRate();
const bRate = b.adjustedCompleteTakerToMakerRate();
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
// resulting in a NaN div by 0 (output)
if (bRate.isNaN()) {
@@ -498,7 +498,7 @@ export function reducePaths(sortedPaths: Path[]): Path[] {
if (!bestNonNativeCompletePath) {
return sortedPaths;
}
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteMakerToTakerRate();
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteTakerToMakerRate();
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
return sortedPaths;
}

View File

@@ -10,7 +10,7 @@ import { ZERO_AMOUNT } from './constants';
* Computes the "complete" rate given the input/output of a path.
* This value penalizes the path if it falls short of the target input.
*/
export function getCompleteRate(
export function getCompleteTakerToMakerRate(
side: MarketOperation,
input: BigNumber,
output: BigNumber,

View File

@@ -479,8 +479,8 @@ export interface OptimizedHop {
inputAmount: BigNumber;
outputAmount: BigNumber;
sourceFlags: bigint;
gasCost: number;
orders: OptimizedOrder[];
adjustedCompleteRate: BigNumber;
}
export interface OptimizerResult {