fix gas/overhead evaluation
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -479,8 +479,8 @@ export interface OptimizedHop {
|
||||
inputAmount: BigNumber;
|
||||
outputAmount: BigNumber;
|
||||
sourceFlags: bigint;
|
||||
gasCost: number;
|
||||
orders: OptimizedOrder[];
|
||||
adjustedCompleteRate: BigNumber;
|
||||
}
|
||||
|
||||
export interface OptimizerResult {
|
||||
|
||||
Reference in New Issue
Block a user