Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend the trade calculation to support lossless mode #14

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion src/strategy-management/Toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export class Toolkit {

const orders = await this._cache.getOrdersByPair(sourceToken, targetToken);
const maxSourceAmountWei = Object.values(orders).reduce(
(acc, order) => acc.add(getEncodedTradeSourceAmount(order.y, order)),
(acc, order) => acc.add(getEncodedTradeSourceAmount(order.y, order, false)),
BigNumber.from(0)
);
const decimals = await this._decimals.fetchDecimals(sourceToken);
Expand Down
67 changes: 39 additions & 28 deletions src/trade-matcher/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,29 @@ import { sortByMaxRate, sortByMinRate } from './utils';

const rateBySourceAmount = (
sourceAmount: BigNumber,
order: EncodedOrder
order: EncodedOrder,
simulated: boolean
): Rate => {
let input = sourceAmount;
let output = tradeTargetAmount(input, order);
let output = tradeTargetAmount(input, order, simulated);
if (output.gt(order.y)) {
input = tradeSourceAmount(order.y, order);
output = tradeTargetAmount(input, order);
input = tradeSourceAmount(order.y, order, simulated);
output = tradeTargetAmount(input, order, simulated);
while (output.gt(order.y)) {
input = input.sub(1);
output = tradeTargetAmount(input, order);
output = tradeTargetAmount(input, order, simulated);
}
}
return { input, output };
};

const rateByTargetAmount = (
targetAmount: BigNumber,
order: EncodedOrder
order: EncodedOrder,
simulated: boolean
): Rate => {
const input = BigNumberMin(targetAmount, order.y);
const output = tradeSourceAmount(input, order);
const output = tradeSourceAmount(input, order, simulated);
return { input, output };
};

Expand All @@ -63,7 +65,7 @@ const equalTargetAmount = (order: EncodedOrder, limit: BigNumber) => {
};

const equalSourceAmount = (order: EncodedOrder, limit: BigNumber) => {
return tradeSourceAmount(equalTargetAmount(order, limit), order);
return tradeSourceAmount(equalTargetAmount(order, limit), order, false);
};

/**
Expand All @@ -79,13 +81,14 @@ const equalSourceAmount = (order: EncodedOrder, limit: BigNumber) => {
const sortedQuotes = (
amount: BigNumber,
ordersMap: OrdersMap,
trade: (amount: BigNumber, order: EncodedOrder) => Rate,
sort: (x: Rate, y: Rate) => number
trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate,
sort: (x: Rate, y: Rate) => number,
simulated: boolean
): Quote[] =>
Object.keys(ordersMap)
.map((id) => ({
id: BigNumber.from(id),
rate: trade(amount, ordersMap[id]),
rate: trade(amount, ordersMap[id], simulated),
}))
.sort((a, b) => sort(a.rate, b.rate));

Expand All @@ -102,7 +105,8 @@ const matchFast = (
ordersMap: OrdersMap,
quotes: Quote[],
filter: Filter,
trade: (amount: BigNumber, order: EncodedOrder) => Rate
trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate,
simulated: boolean
): MatchAction[] => {
const actions: MatchAction[] = [];

Expand All @@ -128,7 +132,7 @@ const matchFast = (
} /* if (amount.lt(rate.input)) */ else {
const adjustedRate: Rate = {
input: amount,
output: trade(amount, ordersMap[quote.id.toString()]).output,
output: trade(amount, ordersMap[quote.id.toString()], simulated).output,
};
if (filter(adjustedRate)) {
actions.push({
Expand Down Expand Up @@ -159,8 +163,9 @@ const matchBest = (
ordersMap: OrdersMap,
quotes: Quote[],
filter: Filter,
trade: (amount: BigNumber, order: EncodedOrder) => Rate,
equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber
trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate,
equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber,
simulated: boolean
): MatchAction[] => {
const order0: EncodedOrder = {
y: BigNumber.from(0),
Expand All @@ -181,7 +186,7 @@ const matchBest = (
limit = getLimit(orders[n]);
rates = orders
.slice(0, n)
.map((order) => trade(equalize(order, limit), order));
.map((order) => trade(equalize(order, limit), order, simulated));
total = rates.reduce((sum, rate) => sum.add(rate.input), BigNumber.from(0));
delta = total.sub(amount);
if (delta.eq(0)) {
Expand All @@ -194,7 +199,7 @@ const matchBest = (
limit = lo.add(hi).div(2);
rates = orders
.slice(0, n)
.map((order) => trade(equalize(order, limit), order));
.map((order) => trade(equalize(order, limit), order, simulated));
total = rates.reduce(
(sum, rate) => sum.add(rate.input),
BigNumber.from(0)
Expand All @@ -214,7 +219,7 @@ const matchBest = (

if (delta.gt(0)) {
for (let i = rates.length - 1; i >= 0; i--) {
const rate = trade(rates[i].input.sub(delta), orders[i]);
const rate = trade(rates[i].input.sub(delta), orders[i], simulated);
delta = delta.add(rate.input.sub(rates[i].input));
rates[i] = rate;
if (delta.lte(0)) {
Expand All @@ -223,7 +228,7 @@ const matchBest = (
}
} else if (delta.lt(0)) {
for (let i = 0; i <= rates.length - 1; i++) {
const rate = trade(rates[i].input.sub(delta), orders[i]);
const rate = trade(rates[i].input.sub(delta), orders[i], simulated);
delta = delta.add(rate.input.sub(rates[i].input));
if (delta.gt(0)) {
break;
Expand All @@ -246,14 +251,15 @@ const matchBy = (
ordersMap: OrdersMap,
matchTypes: MatchType[],
filter: Filter,
trade: (amount: BigNumber, order: EncodedOrder) => Rate,
trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate,
sort: (x: Rate, y: Rate) => number,
equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber
equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber,
simulated: boolean
): MatchOptions => {
const quotes = sortedQuotes(amount, ordersMap, trade, sort);
const quotes = sortedQuotes(amount, ordersMap, trade, sort, simulated);
const res: MatchOptions = {};
if (matchTypes.includes(MatchType.Fast)) {
res[MatchType.Fast] = matchFast(amount, ordersMap, quotes, filter, trade);
res[MatchType.Fast] = matchFast(amount, ordersMap, quotes, filter, trade, simulated);
}
if (matchTypes.includes(MatchType.Best)) {
res[MatchType.Best] = matchBest(
Expand All @@ -262,7 +268,8 @@ const matchBy = (
quotes,
filter,
trade,
equalize
equalize,
simulated
);
}
return res;
Expand All @@ -275,7 +282,8 @@ export const matchBySourceAmount = (
amount: BigNumber,
ordersMap: OrdersMap,
matchTypes: MatchType[],
filter: Filter = defaultFilter
filter: Filter = defaultFilter,
simulated: boolean = false
): MatchOptions => {
return matchBy(
amount,
Expand All @@ -284,15 +292,17 @@ export const matchBySourceAmount = (
filter,
rateBySourceAmount,
sortByMinRate,
equalSourceAmount
equalSourceAmount,
simulated
);
};

export const matchByTargetAmount = (
amount: BigNumber,
ordersMap: OrdersMap,
matchTypes: MatchType[],
filter: Filter = defaultFilter
filter: Filter = defaultFilter,
simulated: boolean = false
): MatchOptions => {
return matchBy(
amount,
Expand All @@ -301,6 +311,7 @@ export const matchByTargetAmount = (
filter,
rateByTargetAmount,
sortByMaxRate,
equalTargetAmount
equalTargetAmount,
simulated
);
};
34 changes: 28 additions & 6 deletions src/trade-matcher/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,18 @@ const getEncodedTradeBySourceAmount = (
y: BigNumber,
z: BigNumber,
A: BigNumber,
B: BigNumber
B: BigNumber,
simulated: boolean
): BigNumber => {
if (simulated) {
const temp1 = z.mul(C);
const temp2 = y.mul(A).add(z.mul(B));
const temp3 = temp2.mul(x);
const temp4 = temp1.mul(temp1);
const temp5 = temp3.mul(A);
return mulDivF(temp2, temp3, temp4.add(temp5));
}

if (A.eq(0)) {
return mulDivF(x, mul(B, B), mul(C, C));
}
Expand Down Expand Up @@ -62,8 +72,18 @@ const getEncodedTradeByTargetAmount = (
y: BigNumber,
z: BigNumber,
A: BigNumber,
B: BigNumber
B: BigNumber,
simulated: boolean
): BigNumber => {
if (simulated) {
const temp1 = z.mul(C);
const temp2 = y.mul(A).add(z.mul(B));
const temp3 = temp2.sub(x.mul(A));
const temp4 = temp1.mul(temp1);
const temp5 = temp2.mul(temp3);
return mulDivC(x, temp4, temp5);
}

if (A.eq(0)) {
return mulDivC(x, mul(C, C), mul(B, B));
}
Expand Down Expand Up @@ -115,31 +135,33 @@ const getDecodedTradeByTargetAmount = (

export const getEncodedTradeTargetAmount = (
amount: BigNumber,
order: EncodedOrder
order: EncodedOrder,
simulated: boolean
): BigNumber => {
const x = amount;
const y = order.y;
const z = order.z;
const A = decodeFloat(order.A);
const B = decodeFloat(order.B);
try {
return uint128(getEncodedTradeBySourceAmount(x, y, z, A, B));
return uint128(getEncodedTradeBySourceAmount(x, y, z, A, B, simulated));
} catch (error) {
return BigNumber.from(0); /* rate = zero / amount = zero */
}
};

export const getEncodedTradeSourceAmount = (
amount: BigNumber,
order: EncodedOrder
order: EncodedOrder,
simulated: boolean
): BigNumber => {
const x = amount;
const y = order.y;
const z = order.z;
const A = decodeFloat(order.A);
const B = decodeFloat(order.B);
try {
return uint128(getEncodedTradeByTargetAmount(x, y, z, A, B));
return uint128(getEncodedTradeByTargetAmount(x, y, z, A, B, simulated));
} catch (error) {
return MAX_UINT128; /* rate = amount / infinity = zero */
}
Expand Down
10 changes: 6 additions & 4 deletions tests/match.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ import BigPoolMatch from './data/BigPoolMatch.json' assert { type: 'json' };
import EthUsdcMatch from './data/EthUsdcMatch.json' assert { type: 'json' };
import SpecialMatch from './data/SpecialMatch.json' assert { type: 'json' };

type TradeMethod = (amount: BigNumber, order: EncodedOrder) => BigNumber;
type TradeMethod = (amount: BigNumber, order: EncodedOrder, simulated: boolean) => BigNumber;
type MatchMethod = 'matchBySourceAmount' | 'matchByTargetAmount';

type MatchFunction = (
amount: BigNumber,
ordersMap: OrdersMap,
matchTypes: MatchType[],
filter: Filter
filter: Filter,
simulated: boolean
) => MatchOptions;

const methods: {
Expand Down Expand Up @@ -122,7 +123,8 @@ describe('Match', () => {
BigNumber.from(test.amount),
ordersMap,
Object.keys(test.actions) as MatchType[],
filter
filter,
false
);
for (const matchType in actions) {
expect(actions[matchType as MatchType]!.length).to.equal(
Expand All @@ -145,7 +147,7 @@ describe('Match', () => {
).to.be.true;
expect(
action.output.eq(
trade(action.input, ordersMap[action.id.toNumber()])
trade(action.input, ordersMap[action.id.toNumber()], false)
)
).to.be.true;
}
Expand Down