Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

AssetSwapper: Fix quote optimizer fee bug #2526

Merged
merged 1 commit into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/asset-swapper/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
{
"note": "Fix fee schedule not being scaled by gas price.",
"pr": 2522
},
{
"note": "Fix quote optimizer bug not properly accounting for fees.",
"pr": 2526
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions packages/asset-swapper/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
]), // Default asset-swapper for CFL oriented fee types
};

// 15 seconds polling interval
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 15000;
// 6 seconds polling interval
const PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS = 6000;
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);

// default 50% buffer for selecting native orders to be aggregated with other sources
Expand Down
12 changes: 7 additions & 5 deletions packages/asset-swapper/src/utils/market_operation_utils/fills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export function getPathAdjustedSize(path: Fill[], targetInput: BigNumber = POSIT
return [input.integerValue(), output.integerValue()];
}

export function isValidPath(path: Fill[]): boolean {
export function isValidPath(path: Fill[], skipDuplicateCheck: boolean = false): boolean {
let flags = 0;
for (let i = 0; i < path.length; ++i) {
// Fill must immediately follow its parent.
Expand All @@ -206,10 +206,12 @@ export function isValidPath(path: Fill[]): boolean {
return false;
}
}
// Fill must not be duplicated.
for (let j = 0; j < i; ++j) {
if (path[i] === path[j]) {
return false;
if (!skipDuplicateCheck) {
// Fill must not be duplicated.
for (let j = 0; j < i; ++j) {
if (path[i] === path[j]) {
return false;
}
}
}
flags |= path[i].flags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,55 @@ function mixPaths(
targetInput: BigNumber,
maxSteps: number = 2 ** 15,
): Fill[] {
const allFills = [...pathA, ...pathB].sort((a, b) => b.rate.comparedTo(a.rate));
let bestPath: Fill[] = [];
let bestPathInput = ZERO_AMOUNT;
let bestPathRate = ZERO_AMOUNT;
let steps = 0;
const _isBetterPath = (input: BigNumber, rate: BigNumber) => {
if (bestPathInput.lt(targetInput)) {
return input.gt(bestPathInput);
} else if (input.gte(bestPathInput)) {
} else if (input.gte(targetInput)) {
return rate.gt(bestPathRate);
}
return false;
};
const _walk = (path: Fill[], input: BigNumber, output: BigNumber) => {
const _walk = (path: Fill[], input: BigNumber, output: BigNumber, allFills: Fill[]) => {
steps += 1;
const rate = getRate(side, input, output);
if (_isBetterPath(input, rate)) {
bestPath = path;
bestPathInput = input;
bestPathRate = rate;
}
if (input.lt(targetInput)) {
for (const fill of allFills) {
if (steps >= maxSteps) {
const remainingInput = targetInput.minus(input);
if (remainingInput.gt(0)) {
for (let i = 0; i < allFills.length; ++i) {
const fill = allFills[i];
if (steps + 1 >= maxSteps) {
break;
}
const childPath = [...path, fill];
if (!isValidPath(childPath)) {
if (!isValidPath(childPath, true)) {
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;
}

Expand All @@ -77,6 +91,14 @@ function isPathComplete(path: Fill[], targetInput: BigNumber): boolean {
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);
Comment on lines +98 to +99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is negative right? If so it would be clearer if it was:

Suggested change
const penalty = fill.adjustedOutput.minus(fill.output);
return fill.output.times(remainingInput.div(fill.input)).plus(penalty);
const penalty = fill.output.minus(fill.adjustedOutput);
return fill.output.times(remainingInput.div(fill.input)).minus(penalty);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, it can be positive for buys.

}

function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
if (input.eq(0) || output.eq(0)) {
return ZERO_AMOUNT;
Expand Down