Skip to content

Commit

Permalink
Merge pull request #2321 from zeitgeistpm/tr-amm2-validation
Browse files Browse the repository at this point in the history
AMM2 validation
  • Loading branch information
Robiquet authored Feb 27, 2024
2 parents 9890377 + fca7271 commit 54adbf4
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 1 deletion.
18 changes: 18 additions & 0 deletions components/trade-form/BuyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
approximateMaxAmountInForBuy,
calculateSpotPrice,
calculateSwapAmountOutForBuy,
isValidBuyAmount,
} from "lib/util/amm2";
import { formatNumberCompact } from "lib/util/format-compact";
import { parseAssetIdString } from "lib/util/parse-asset-id";
Expand Down Expand Up @@ -93,6 +94,21 @@ const BuyForm = ({
const assetReserve =
pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset);

const validBuy = useMemo(() => {
return (
assetReserve &&
pool.liquidity &&
swapFee &&
isValidBuyAmount(
assetReserve,
amountIn,
pool.liquidity,
swapFee,
creatorFee,
)
);
}, [assetReserve, pool?.liquidity, amountIn]);

const maxAmountIn = useMemo(() => {
return (
assetReserve &&
Expand Down Expand Up @@ -274,6 +290,8 @@ const BuyForm = ({
return `Maximum amount of ${baseSymbol} that can be traded is ${maxAmountIn
.div(ZTG)
.toFixed(3)}`;
} else if (validBuy?.isValid === false) {
return validBuy.message;
}
},
})}
Expand Down
12 changes: 12 additions & 0 deletions components/trade-form/SellForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
approximateMaxAmountInForSell,
calculateSpotPrice,
calculateSwapAmountOutForSell,
isValidSellAmount,
} from "lib/util/amm2";
import { formatNumberCompact } from "lib/util/format-compact";
import { parseAssetIdString } from "lib/util/parse-asset-id";
Expand Down Expand Up @@ -95,6 +96,15 @@ const SellForm = ({
const assetReserve =
pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset);

const validSell = useMemo(() => {
return (
assetReserve &&
pool.liquidity &&
swapFee &&
isValidSellAmount(assetReserve, amountIn, pool.liquidity)
);
}, [assetReserve, pool?.liquidity, amountIn]);

const maxAmountIn = useMemo(() => {
return (
assetReserve &&
Expand Down Expand Up @@ -243,6 +253,8 @@ const SellForm = ({
return `Maximum amount that can be traded is ${maxAmountIn
.div(ZTG)
.toFixed(3)}`;
} else if (validSell?.isValid === false) {
return validSell.message;
}
},
})}
Expand Down
94 changes: 93 additions & 1 deletion lib/util/amm2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
calculateSwapAmountOutForBuy,
calculateSwapAmountOutForSell,
calculatePoolAmounts,
isValidBuyAmount,
isValidSellAmount,
calculateReserveAfterSell,
} from "./amm2";
import { ZTG } from "@zeitgeistpm/sdk";

// test cases copied from https://github.com/zeitgeistpm/zeitgeist/blob/f0586d32c692f738b04d03bec4e59a73d6899182/zrml/neo-swaps/src/math.rs
describe("amm2", () => {
Expand All @@ -26,7 +30,7 @@ describe("amm2", () => {
test("should work with fees", () => {
const amountOut = calculateSwapAmountOutForBuy(
new Decimal(1_000_000_000_000),
new Decimal(109270000000),
new Decimal(109_270_000_000),
new Decimal(1_442_695_040_889),
new Decimal(0.03),
new Decimal(0.005),
Expand Down Expand Up @@ -147,6 +151,94 @@ describe("amm2", () => {
});
});

describe("isValidBuyAmount", () => {
test("should return true if amount in is allowed", () => {
const { isValid, message } = isValidBuyAmount(
new Decimal(10 * 10 ** 10),
new Decimal(10 * 10 ** 10),
new Decimal(144269504088),
new Decimal(0),
new Decimal(0),
);

expect(isValid).toEqual(true);
expect(message).toEqual(undefined);
});

test("should return false if amount in is too high", () => {
const { isValid, message } = isValidBuyAmount(
new Decimal(10 * 10 ** 10),
new Decimal(10000 * 10 ** 10),
new Decimal(144269504088),
new Decimal(0),
new Decimal(0),
);

expect(isValid).toEqual(false);
expect(message).toEqual("Amount in too high");
});

test("should return false if amount in is too low", () => {
const { isValid, message } = isValidBuyAmount(
new Decimal(100 * 10 ** 10),
new Decimal(1 * 10 ** 10),
new Decimal(144269504088),
new Decimal(0),
new Decimal(0),
);

expect(isValid).toEqual(false);
expect(message).toEqual("Amount in too low");
});
});

describe("isValidSellAmount", () => {
test("should return true if amount in is allowed", () => {
const { isValid, message } = isValidSellAmount(
new Decimal(10 * 10 ** 10),
new Decimal(10 * 10 ** 10),
new Decimal(144269504088),
);

expect(isValid).toEqual(true);
expect(message).toEqual(undefined);
});

test("should return false if amount in is too high ", () => {
const { isValid, message } = isValidSellAmount(
new Decimal(10 * 10 ** 10),
new Decimal(10000 * 10 ** 10),
new Decimal(144269504088),
);

expect(isValid).toEqual(false);
expect(message).toEqual("Amount in too high");
});

test("should return false if price is too low", () => {
const { isValid, message } = isValidSellAmount(
new Decimal(1000 * 10 ** 10),
new Decimal(10 * 10 ** 10),
new Decimal(144269504088),
);

expect(isValid).toEqual(false);
expect(message).toEqual("Price is low to sell");
});
});

describe("calculateReserveAfterSell", () => {
test("should work", () => {
const newReserve = calculateReserveAfterSell(
new Decimal(10 * 10 ** 10),
new Decimal(10 * 10 ** 10),
new Decimal(144269504088),
);

expect(newReserve.div(ZTG).toFixed(3)).toEqual("15.850");
});
});

test("all functions", () => {
const liquidity = new Decimal(144.00003701590745);
const reserves = [
Expand Down
83 changes: 83 additions & 0 deletions lib/util/amm2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,86 @@ export const calculatePoolAmounts = (

return poolAmounts;
};

export const isValidBuyAmount = (
assetReserve: Decimal,
amountIn: Decimal,
liquidityParameter: Decimal,
poolFee: Decimal, // 1% is 0.01
creatorFee: Decimal, // 1% is 0.01
) => {
const totalFee = poolFee.plus(creatorFee);
const feeMultiplier = new Decimal(1).minus(totalFee);
const amountInMinusFees = amountIn.mul(feeMultiplier);

if (
amountInMinusFees.greaterThanOrEqualTo(
calculatePoolNumericalThreshold(liquidityParameter),
)
) {
return { isValid: false, message: "Amount in too high" };
} else if (
calculateBuyLnArgument(
assetReserve,
amountInMinusFees,
liquidityParameter,
).lessThanOrEqualTo(lsmrConstant)
) {
return { isValid: false, message: "Amount in too low" };
} else {
return { isValid: true };
}
};

export const isValidSellAmount = (
assetReserve: Decimal,
amountIn: Decimal,
liquidityParameter: Decimal,
) => {
const numericalThreshold =
calculatePoolNumericalThreshold(liquidityParameter);
if (assetReserve.greaterThanOrEqualTo(numericalThreshold)) {
return { isValid: false, message: "Price is low to sell" };
} else if (amountIn.greaterThanOrEqualTo(numericalThreshold)) {
return { isValid: false, message: "Amount in too high" };
} else if (
amountIn.greaterThanOrEqualTo(numericalThreshold) ||
calculateReserveAfterSell(
assetReserve,
amountIn,
liquidityParameter,
).greaterThanOrEqualTo(numericalThreshold)
) {
return { isValid: false, message: "Amount in too high" };
} else {
return { isValid: true };
}
};

export const calculateReserveAfterSell = (
assetReserve: Decimal,
amountIn: Decimal,
liquidity: Decimal, // liqudity parameter of the pool
) => {
// new_reserve = old_reserve + b ln(exp(old_reserve/liquidity_param) - 1 + exp(-amount/liquidity_param))
const term1 = assetReserve.div(liquidity).exp();
const term2 = new Decimal(0).minus(amountIn).div(liquidity).exp();

return term1.plus(term2).minus(1).ln().mul(liquidity).plus(assetReserve);
};

const lsmrConstant = 0.1;

const calculatePoolNumericalThreshold = (liquidityParameter: Decimal) =>
liquidityParameter.mul(10);

const calculateBuyLnArgument = (
assetReserve: Decimal,
amountInMinusFee: Decimal,
liquidity: Decimal, // liqudity parameter of the pool
) => {
const term1 = amountInMinusFee.div(liquidity).exp();
const term2 = new Decimal(0).minus(assetReserve).div(liquidity).exp();

return term1.plus(term2).minus(1);
};

0 comments on commit 54adbf4

Please sign in to comment.