Merge pull request #2526 from 0xProject/fix/asset-swapper/optimizer-fee-bugs

AssetSwapper: Fix quote optimizer fee bug
This commit is contained in:
Lawrence Forman 2020-03-24 09:56:09 -04:00 committed by GitHub
commit 99b0a95f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 16 deletions

View File

@ -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
} }
] ]
}, },

View File

@ -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

View File

@ -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;

View File

@ -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;