From 86ecc083fe7c22027639964323d309b7583eeb52 Mon Sep 17 00:00:00 2001 From: DevRozaDev <158298065+DevRozaDev@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:39:00 +0200 Subject: [PATCH] add swapWithSlippage and the associated math functions --- src/invariant.ts | 51 +++++++++++- src/math.ts | 58 +++++++++++++ src/snippets.ts | 3 +- src/testUtils.ts | 4 - test/cross.test.ts | 2 +- test/cross_both_side.test.ts | 3 +- ...tion_with_pool_on_removed_fee_tier.test.ts | 4 +- test/limits.test.ts | 1 - test/liquidity_gap.test.ts | 2 +- test/math.test.ts | 83 ++++++++++++++++++- test/pool-with-alph.test.ts | 3 +- test/position.test.ts | 2 +- test/position_list.test.ts | 3 +- test/position_slippage.test.ts | 3 +- test/swap.test.ts | 2 +- 15 files changed, 200 insertions(+), 24 deletions(-) diff --git a/src/invariant.ts b/src/invariant.ts index 373a8c12..a0b68fd4 100644 --- a/src/invariant.ts +++ b/src/invariant.ts @@ -14,7 +14,7 @@ import { WithdrawProtocolFee } from '../artifacts/ts' import { FeeTier, Pool, PoolKey, Position, QuoteResult, Tick } from '../artifacts/ts/types' -import { calculateTick } from './math' +import { calculateSqrtPriceAfterSlippage, calculateTick } from './math' import { Network } from './network' import { getReserveAddress } from './testUtils' import { @@ -36,7 +36,6 @@ import { Address, ALPH_TOKEN_ID, DUST_AMOUNT, - ONE_ALPH, SignerProvider, TransactionBuilder } from '@alephium/web3' @@ -390,6 +389,52 @@ export class Invariant { return await signAndSend(signer, tx) } + async swapWithSlippageTx( + signer: SignerProvider, + poolKey: PoolKey, + xToY: boolean, + amount: bigint, + byAmountIn: boolean, + estimatedSqrtPrice: bigint, + slippage: bigint + ) { + const sqrtPriceAfterSlippage = calculateSqrtPriceAfterSlippage( + estimatedSqrtPrice, + slippage, + !xToY + ) + + return this.swapTx( + signer, + poolKey, + xToY, + amount, + byAmountIn, + xToY ? sqrtPriceAfterSlippage - 1n : sqrtPriceAfterSlippage + 1n + ) + } + + async swapWithSlippage( + signer: SignerProvider, + poolKey: PoolKey, + xToY: boolean, + amount: bigint, + byAmountIn: boolean, + estimatedSqrtPrice: bigint, + slippage: bigint + ): Promise { + const tx = await this.swapWithSlippageTx( + signer, + poolKey, + xToY, + amount, + byAmountIn, + estimatedSqrtPrice, + slippage + ) + return await signAndSend(signer, tx) + } + async feeTierExist(feeTier: FeeTier): Promise { return (await this.instance.view.feeTierExist({ args: { feeTier } })).returns } @@ -449,7 +494,7 @@ export class Invariant { async getAllPoolKeys() { return decodePoolKeys((await this.instance.view.getAllPoolKeys()).returns) } - // async swapWithSlippage() {} + // async getPositionTicks() {} // async getRawTickmap() {} // async getFullTickmap() {} diff --git a/src/math.ts b/src/math.ts index 7b7e45f9..f216dbc7 100644 --- a/src/math.ts +++ b/src/math.ts @@ -1,5 +1,6 @@ import { CLAMM, Utils } from '../artifacts/ts' import { LiquidityResult, Pool, Position, SingleTokenLiquidity, Tick } from '../artifacts/ts/types' +import { LiquidityScale, PercentageScale, SqrtPriceScale } from './consts' export const calculateSqrtPrice = async (tickIndex: bigint): Promise => { return ( @@ -204,3 +205,60 @@ export const calculateTokenAmounts = async ( }) ).returns } + +const sqrt = (value: bigint): bigint => { + if (value < 0n) { + throw 'square root of negative numbers is not supported' + } + + if (value < 2n) { + return value + } + + return newtonIteration(value, 1n) +} + +const newtonIteration = (n: bigint, x0: bigint): bigint => { + const x1 = (n / x0 + x0) >> 1n + if (x0 === x1 || x0 === x1 - 1n) { + return x0 + } + return newtonIteration(n, x1) +} + +export const sqrtPriceToPrice = (sqrtPrice: bigint): bigint => { + return (sqrtPrice * sqrtPrice) / toSqrtPrice(1n) +} + +export const priceToSqrtPrice = (price: bigint): bigint => { + return sqrt(price * toSqrtPrice(1n)) +} + +export const calculateSqrtPriceAfterSlippage = ( + sqrtPrice: bigint, + slippage: bigint, + up: boolean +): bigint => { + if (slippage === 0n) { + return sqrtPrice + } + + const multiplier = toPercentage(1n) + (up ? slippage : -slippage) + const price = sqrtPriceToPrice(sqrtPrice) + const priceWithSlippage = price * multiplier * toPercentage(1n) + const sqrtPriceWithSlippage = priceToSqrtPrice(priceWithSlippage) / toPercentage(1n) + + return sqrtPriceWithSlippage +} + +export const toLiquidity = (value: bigint, offset = 0n) => { + return value * 10n ** (LiquidityScale - offset) +} + +export const toSqrtPrice = (value: bigint, offset = 0n) => { + return value * 10n ** (SqrtPriceScale - offset) +} + +export const toPercentage = (value: bigint, offset = 0n) => { + return value * 10n ** (PercentageScale - offset) +} diff --git a/src/snippets.ts b/src/snippets.ts index fde63585..d9dc5059 100644 --- a/src/snippets.ts +++ b/src/snippets.ts @@ -9,14 +9,13 @@ import { initPosition, initSwap, initTokensXY, - toLiquidity, transferPosition, verifyPositionList, withdrawTokens } from './testUtils' import { balanceOf, deployInvariant, newFeeTier, newPoolKey } from './utils' import { PrivateKeyWallet } from '@alephium/web3-wallet' -import { calculateSqrtPrice } from './math' +import { calculateSqrtPrice, toLiquidity } from './math' type TokenInstance = TokenFaucetInstance diff --git a/src/testUtils.ts b/src/testUtils.ts index c31d1d4b..c0dc0f96 100644 --- a/src/testUtils.ts +++ b/src/testUtils.ts @@ -385,7 +385,3 @@ export const getReserveBalances = async (invariant: InvariantInstance, poolKey: const y = await balanceOf(poolKey.tokenY, reserveY) return { x, y } } - -export const toLiquidity = (value: bigint) => { - return value * 10n ** LiquidityScale -} diff --git a/test/cross.test.ts b/test/cross.test.ts index d7e12ee6..cdff55bc 100644 --- a/test/cross.test.ts +++ b/test/cross.test.ts @@ -16,9 +16,9 @@ import { initFeeTier, initPosition, initSwap, - toLiquidity, withdrawTokens } from '../src/testUtils' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') let admin: PrivateKeyWallet diff --git a/test/cross_both_side.test.ts b/test/cross_both_side.test.ts index 2334b23a..76cd126f 100644 --- a/test/cross_both_side.test.ts +++ b/test/cross_both_side.test.ts @@ -10,11 +10,10 @@ import { initFeeTier, initPosition, initSwap, - toLiquidity, withdrawTokens } from '../src/testUtils' import { InvariantError, MaxSqrtPrice, MinSqrtPrice } from '../src/consts' -import { calculateSqrtPrice } from '../src/math' +import { calculateSqrtPrice, toLiquidity } from '../src/math' import { InvariantInstance, TokenFaucetInstance } from '../artifacts/ts' web3.setCurrentNodeProvider('http://127.0.0.1:22973') diff --git a/test/interaction_with_pool_on_removed_fee_tier.test.ts b/test/interaction_with_pool_on_removed_fee_tier.test.ts index fedadf4a..444ebbd4 100644 --- a/test/interaction_with_pool_on_removed_fee_tier.test.ts +++ b/test/interaction_with_pool_on_removed_fee_tier.test.ts @@ -16,8 +16,7 @@ import { removePosition, getPosition, expectError, - getReserveBalances, - toLiquidity + getReserveBalances } from '../src/testUtils' import { ChangeFeeReceiver, @@ -28,6 +27,7 @@ import { WithdrawProtocolFee } from '../artifacts/ts' import { FeeTier, PoolKey } from '../artifacts/ts/types' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') let admin: PrivateKeyWallet diff --git a/test/limits.test.ts b/test/limits.test.ts index b0dd478f..13ae5287 100644 --- a/test/limits.test.ts +++ b/test/limits.test.ts @@ -12,7 +12,6 @@ import { } from '../src/consts' import { getPool, - getReserveAddress, getReserveBalances, initFeeTier, initPool, diff --git a/test/liquidity_gap.test.ts b/test/liquidity_gap.test.ts index abace958..52e974b3 100644 --- a/test/liquidity_gap.test.ts +++ b/test/liquidity_gap.test.ts @@ -14,11 +14,11 @@ import { initPosition, initSwap, initTokensXY, - toLiquidity, quote, withdrawTokens } from '../src/testUtils' import { FeeTier, PoolKey } from '../artifacts/ts/types' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') diff --git a/test/math.test.ts b/test/math.test.ts index 3f8a338f..9d94da43 100644 --- a/test/math.test.ts +++ b/test/math.test.ts @@ -2,9 +2,12 @@ import { web3 } from '@alephium/web3' import { calculateFee, calculateSqrtPrice, + calculateSqrtPriceAfterSlippage, getLiquidity, getLiquidityByX, - getLiquidityByY + getLiquidityByY, + toPercentage, + toSqrtPrice } from '../src/math' import { expectError } from '../src/testUtils' import { UtilsError } from '../src/consts' @@ -258,4 +261,82 @@ describe('math spec', () => { expect(y).toBe(0n) }) }) + describe('test calculateSqrtPriceAfterSlippage', () => { + test('no slippage up', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(0n, 0n) + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, true) + expect(limitSqrt).toBe(sqrtPrice) + }) + test('no slippage down', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(0n, 0n) + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, false) + expect(limitSqrt).toBe(sqrtPrice) + }) + test('slippage of 1% up', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(1n, 2n) + // sqrt(1) * sqrt(1 + 0.01) = 1.0049876 + const expected = 1004987562112089027021926n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, true) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 1% down', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(1n, 2n) + // sqrt(1) * sqrt(1 - 0.01) = 0.99498744 + const expected = 994987437106619954734479n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, false) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 0.5% up', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(5n, 3n) + // sqrt(1) * sqrt(1 - 0.005) = 1.00249688 + const expected = 1002496882788171067537936n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, true) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 0.5% down', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(5n, 3n) + // sqrt(1) * sqrt(1 - 0.005) = 0.997496867 + const expected = 997496867163000166582694n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, false) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 0.00003% up', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(3n, 7n) + // sqrt(1) * sqrt(1 + 0.0000003) = 1.00000015 + const expected = 1000000149999988750001687n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, true) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 0.00003% down', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(3n, 7n) + // sqrt(1) * sqrt(1 - 0.0000003) = 0.99999985 + const expected = 999999849999988749998312n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, false) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 100% up', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(1n, 0n) + // sqrt(1) * sqrt(1 + 1) = 1.414213562373095048801688... + const expected = 1414213562373095048801688n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, true) + expect(limitSqrt).toBe(expected) + }) + test('slippage of 100% down', () => { + const sqrtPrice = toSqrtPrice(1n, 0n) + const slippage = toPercentage(1n, 0n) + // sqrt(1) * sqrt(1 - 1) = 0 + const expected = 0n + const limitSqrt = calculateSqrtPriceAfterSlippage(sqrtPrice, slippage, false) + expect(limitSqrt).toBe(expected) + }) + }) }) diff --git a/test/pool-with-alph.test.ts b/test/pool-with-alph.test.ts index 969333a0..8d0daec8 100644 --- a/test/pool-with-alph.test.ts +++ b/test/pool-with-alph.test.ts @@ -3,12 +3,13 @@ import { getSigner } from '@alephium/web3-test' import { PrivateKeyWallet } from '@alephium/web3-wallet' import { balanceOf, newFeeTier, newPoolKey } from '../src/utils' import { MinSqrtPrice, PercentageScale } from '../src/consts' -import { initTokensXY, toLiquidity, withdrawTokens } from '../src/testUtils' +import { initTokensXY, withdrawTokens } from '../src/testUtils' import { TokenFaucetInstance } from '../artifacts/ts' import { FeeTier, PoolKey } from '../artifacts/ts/types' import { Invariant } from '../src/invariant' import { Network } from '../src/network' import { getBasicFeeTickSpacing } from '../src/snippets' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') diff --git a/test/position.test.ts b/test/position.test.ts index d1323daf..9a206154 100644 --- a/test/position.test.ts +++ b/test/position.test.ts @@ -15,11 +15,11 @@ import { initSwap, initTokensXY, isTickInitialized, - toLiquidity, removePosition, withdrawTokens } from '../src/testUtils' import { InvariantError, MaxSqrtPrice, MinSqrtPrice, PercentageScale } from '../src/consts' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') diff --git a/test/position_list.test.ts b/test/position_list.test.ts index b0c87052..6ebdaa2e 100644 --- a/test/position_list.test.ts +++ b/test/position_list.test.ts @@ -12,13 +12,12 @@ import { initPosition, initTokensXY, isTickInitialized, - toLiquidity, removePosition, transferPosition, verifyPositionList, withdrawTokens } from '../src/testUtils' -import { calculateSqrtPrice } from '../src/math' +import { calculateSqrtPrice, toLiquidity } from '../src/math' import { InvariantError, MaxSqrtPrice, PercentageScale } from '../src/consts' import { deployInvariant, newFeeTier, newPoolKey } from '../src/utils' import { InvariantInstance, TokenFaucetInstance } from '../artifacts/ts' diff --git a/test/position_slippage.test.ts b/test/position_slippage.test.ts index d7da50e9..ee2face1 100644 --- a/test/position_slippage.test.ts +++ b/test/position_slippage.test.ts @@ -10,10 +10,9 @@ import { initPool, initPosition, initTokensXY, - toLiquidity, withdrawTokens } from '../src/testUtils' -import { calculateSqrtPrice } from '../src/math' +import { calculateSqrtPrice, toLiquidity } from '../src/math' import { InvariantError } from '../src/consts' import { InvariantInstance, TokenFaucetInstance } from '../artifacts/ts' diff --git a/test/swap.test.ts b/test/swap.test.ts index 1c657d5e..60cd8071 100644 --- a/test/swap.test.ts +++ b/test/swap.test.ts @@ -19,11 +19,11 @@ import { initPosition, initSwap, initTokensXY, - toLiquidity, quote, withdrawTokens } from '../src/testUtils' import { InvariantError, MaxSqrtPrice, MinSqrtPrice, PercentageScale, VMError } from '../src/consts' +import { toLiquidity } from '../src/math' web3.setCurrentNodeProvider('http://127.0.0.1:22973') let admin: PrivateKeyWallet