Merge pull request #2526 from 0xProject/fix/asset-swapper/optimizer-fee-bugs
AssetSwapper: Fix quote optimizer fee bug
This commit is contained in:
commit
99b0a95f8a
@ -29,6 +29,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Fix fee schedule not being scaled by gas price.",
|
"note": "Fix fee schedule not being scaled by gas price.",
|
||||||
"pr": 2522
|
"pr": 2522
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Fix quote optimizer bug not properly accounting for fees.",
|
||||||
|
"pr": 2526
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -29,8 +29,8 @@ const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
|||||||
]), // Default asset-swapper for CFL oriented fee types
|
]), // Default asset-swapper for CFL oriented fee types
|
||||||
};
|
};
|
||||||
|
|
||||||
// 15 seconds polling interval
|
// 6 seconds polling interval
|
||||||
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 15000;
|
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 6000;
|
||||||
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
|
||||||
|
|
||||||
// default 50% buffer for selecting native orders to be aggregated with other sources
|
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||||
|
@ -197,7 +197,7 @@ export function getPathAdjustedSize(path: Fill[], targetInput: BigNumber = POSIT
|
|||||||
return [input.integerValue(), output.integerValue()];
|
return [input.integerValue(), output.integerValue()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidPath(path: Fill[]): boolean {
|
export function isValidPath(path: Fill[], skipDuplicateCheck: boolean = false): boolean {
|
||||||
let flags = 0;
|
let flags = 0;
|
||||||
for (let i = 0; i < path.length; ++i) {
|
for (let i = 0; i < path.length; ++i) {
|
||||||
// Fill must immediately follow its parent.
|
// Fill must immediately follow its parent.
|
||||||
@ -206,12 +206,14 @@ export function isValidPath(path: Fill[]): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!skipDuplicateCheck) {
|
||||||
// Fill must not be duplicated.
|
// Fill must not be duplicated.
|
||||||
for (let j = 0; j < i; ++j) {
|
for (let j = 0; j < i; ++j) {
|
||||||
if (path[i] === path[j]) {
|
if (path[i] === path[j]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
flags |= path[i].flags;
|
flags |= path[i].flags;
|
||||||
}
|
}
|
||||||
const conflictFlags = FillFlags.Kyber | FillFlags.ConflictsWithKyber;
|
const conflictFlags = FillFlags.Kyber | FillFlags.ConflictsWithKyber;
|
||||||
|
@ -34,7 +34,6 @@ function mixPaths(
|
|||||||
targetInput: BigNumber,
|
targetInput: BigNumber,
|
||||||
maxSteps: number = 2 ** 15,
|
maxSteps: number = 2 ** 15,
|
||||||
): Fill[] {
|
): Fill[] {
|
||||||
const allFills = [...pathA, ...pathB].sort((a, b) => b.rate.comparedTo(a.rate));
|
|
||||||
let bestPath: Fill[] = [];
|
let bestPath: Fill[] = [];
|
||||||
let bestPathInput = ZERO_AMOUNT;
|
let bestPathInput = ZERO_AMOUNT;
|
||||||
let bestPathRate = ZERO_AMOUNT;
|
let bestPathRate = ZERO_AMOUNT;
|
||||||
@ -42,12 +41,12 @@ function mixPaths(
|
|||||||
const _isBetterPath = (input: BigNumber, rate: BigNumber) => {
|
const _isBetterPath = (input: BigNumber, rate: BigNumber) => {
|
||||||
if (bestPathInput.lt(targetInput)) {
|
if (bestPathInput.lt(targetInput)) {
|
||||||
return input.gt(bestPathInput);
|
return input.gt(bestPathInput);
|
||||||
} else if (input.gte(bestPathInput)) {
|
} else if (input.gte(targetInput)) {
|
||||||
return rate.gt(bestPathRate);
|
return rate.gt(bestPathRate);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const _walk = (path: Fill[], input: BigNumber, output: BigNumber) => {
|
const _walk = (path: Fill[], input: BigNumber, output: BigNumber, allFills: Fill[]) => {
|
||||||
steps += 1;
|
steps += 1;
|
||||||
const rate = getRate(side, input, output);
|
const rate = getRate(side, input, output);
|
||||||
if (_isBetterPath(input, rate)) {
|
if (_isBetterPath(input, rate)) {
|
||||||
@ -55,20 +54,35 @@ function mixPaths(
|
|||||||
bestPathInput = input;
|
bestPathInput = input;
|
||||||
bestPathRate = rate;
|
bestPathRate = rate;
|
||||||
}
|
}
|
||||||
if (input.lt(targetInput)) {
|
const remainingInput = targetInput.minus(input);
|
||||||
for (const fill of allFills) {
|
if (remainingInput.gt(0)) {
|
||||||
if (steps >= maxSteps) {
|
for (let i = 0; i < allFills.length; ++i) {
|
||||||
|
const fill = allFills[i];
|
||||||
|
if (steps + 1 >= maxSteps) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const childPath = [...path, fill];
|
const childPath = [...path, fill];
|
||||||
if (!isValidPath(childPath)) {
|
if (!isValidPath(childPath, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_walk(childPath, input.plus(fill.input), output.plus(fill.adjustedOutput));
|
// Remove this fill from the next list of candidate fills.
|
||||||
|
const nextAllFills = allFills.slice();
|
||||||
|
nextAllFills.splice(i, 1);
|
||||||
|
// Recurse.
|
||||||
|
_walk(
|
||||||
|
childPath,
|
||||||
|
input.plus(BigNumber.min(remainingInput, fill.input)),
|
||||||
|
output.plus(
|
||||||
|
// Clip the output of the next fill to the remaining
|
||||||
|
// input.
|
||||||
|
clipFillAdjustedOutput(fill, remainingInput),
|
||||||
|
),
|
||||||
|
nextAllFills,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_walk(bestPath, ZERO_AMOUNT, ZERO_AMOUNT);
|
_walk(bestPath, ZERO_AMOUNT, ZERO_AMOUNT, [...pathA, ...pathB].sort((a, b) => b.rate.comparedTo(a.rate)));
|
||||||
return bestPath;
|
return bestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +91,14 @@ function isPathComplete(path: Fill[], targetInput: BigNumber): boolean {
|
|||||||
return input.gte(targetInput);
|
return input.gte(targetInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clipFillAdjustedOutput(fill: Fill, remainingInput: BigNumber): BigNumber {
|
||||||
|
if (fill.input.lte(remainingInput)) {
|
||||||
|
return fill.adjustedOutput;
|
||||||
|
}
|
||||||
|
const penalty = fill.adjustedOutput.minus(fill.output);
|
||||||
|
return fill.output.times(remainingInput.div(fill.input)).plus(penalty);
|
||||||
|
}
|
||||||
|
|
||||||
function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
|
function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
|
||||||
if (input.eq(0) || output.eq(0)) {
|
if (input.eq(0) || output.eq(0)) {
|
||||||
return ZERO_AMOUNT;
|
return ZERO_AMOUNT;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user