infrastructure-upgrade/lib/v3-core/test/SwapMath.spec.ts

325 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2023-04-08 18:46:18 +00:00
import { BigNumber } from 'ethers'
import { ethers } from 'hardhat'
import { SwapMathTest } from '../typechain/SwapMathTest'
import { expect } from './shared/expect'
import snapshotGasCost from './shared/snapshotGasCost'
import { encodePriceSqrt, expandTo18Decimals } from './shared/utilities'
import { SqrtPriceMathTest } from '../typechain/SqrtPriceMathTest'
describe('SwapMath', () => {
let swapMath: SwapMathTest
let sqrtPriceMath: SqrtPriceMathTest
before(async () => {
const swapMathTestFactory = await ethers.getContractFactory('SwapMathTest')
const sqrtPriceMathTestFactory = await ethers.getContractFactory('SqrtPriceMathTest')
swapMath = (await swapMathTestFactory.deploy()) as SwapMathTest
sqrtPriceMath = (await sqrtPriceMathTestFactory.deploy()) as SqrtPriceMathTest
})
describe('#computeSwapStep', () => {
it('exact amount in that gets capped at price target in one for zero', async () => {
const price = encodePriceSqrt(1, 1)
const priceTarget = encodePriceSqrt(101, 100)
const liquidity = expandTo18Decimals(2)
const amount = expandTo18Decimals(1)
const fee = 600
const zeroForOne = false
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
price,
priceTarget,
liquidity,
amount,
fee
)
expect(amountIn).to.eq('9975124224178055')
expect(feeAmount).to.eq('5988667735148')
expect(amountOut).to.eq('9925619580021728')
expect(amountIn.add(feeAmount), 'entire amount is not used').to.lt(amount)
const priceAfterWholeInputAmount = await sqrtPriceMath.getNextSqrtPriceFromInput(
price,
liquidity,
amount,
zeroForOne
)
expect(sqrtQ, 'price is capped at price target').to.eq(priceTarget)
expect(sqrtQ, 'price is less than price after whole input amount').to.lt(priceAfterWholeInputAmount)
})
it('exact amount out that gets capped at price target in one for zero', async () => {
const price = encodePriceSqrt(1, 1)
const priceTarget = encodePriceSqrt(101, 100)
const liquidity = expandTo18Decimals(2)
const amount = expandTo18Decimals(1).mul(-1)
const fee = 600
const zeroForOne = false
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
price,
priceTarget,
liquidity,
amount,
fee
)
expect(amountIn).to.eq('9975124224178055')
expect(feeAmount).to.eq('5988667735148')
expect(amountOut).to.eq('9925619580021728')
expect(amountOut, 'entire amount out is not returned').to.lt(amount.mul(-1))
const priceAfterWholeOutputAmount = await sqrtPriceMath.getNextSqrtPriceFromOutput(
price,
liquidity,
amount.mul(-1),
zeroForOne
)
expect(sqrtQ, 'price is capped at price target').to.eq(priceTarget)
expect(sqrtQ, 'price is less than price after whole output amount').to.lt(priceAfterWholeOutputAmount)
})
it('exact amount in that is fully spent in one for zero', async () => {
const price = encodePriceSqrt(1, 1)
const priceTarget = encodePriceSqrt(1000, 100)
const liquidity = expandTo18Decimals(2)
const amount = expandTo18Decimals(1)
const fee = 600
const zeroForOne = false
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
price,
priceTarget,
liquidity,
amount,
fee
)
expect(amountIn).to.eq('999400000000000000')
expect(feeAmount).to.eq('600000000000000')
expect(amountOut).to.eq('666399946655997866')
expect(amountIn.add(feeAmount), 'entire amount is used').to.eq(amount)
const priceAfterWholeInputAmountLessFee = await sqrtPriceMath.getNextSqrtPriceFromInput(
price,
liquidity,
amount.sub(feeAmount),
zeroForOne
)
expect(sqrtQ, 'price does not reach price target').to.be.lt(priceTarget)
expect(sqrtQ, 'price is equal to price after whole input amount').to.eq(priceAfterWholeInputAmountLessFee)
})
it('exact amount out that is fully received in one for zero', async () => {
const price = encodePriceSqrt(1, 1)
const priceTarget = encodePriceSqrt(10000, 100)
const liquidity = expandTo18Decimals(2)
const amount = expandTo18Decimals(1).mul(-1)
const fee = 600
const zeroForOne = false
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
price,
priceTarget,
liquidity,
amount,
fee
)
expect(amountIn).to.eq('2000000000000000000')
expect(feeAmount).to.eq('1200720432259356')
expect(amountOut).to.eq(amount.mul(-1))
const priceAfterWholeOutputAmount = await sqrtPriceMath.getNextSqrtPriceFromOutput(
price,
liquidity,
amount.mul(-1),
zeroForOne
)
expect(sqrtQ, 'price does not reach price target').to.be.lt(priceTarget)
expect(sqrtQ, 'price is less than price after whole output amount').to.eq(priceAfterWholeOutputAmount)
})
it('amount out is capped at the desired amount out', async () => {
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
BigNumber.from('417332158212080721273783715441582'),
BigNumber.from('1452870262520218020823638996'),
'159344665391607089467575320103',
'-1',
1
)
expect(amountIn).to.eq('1')
expect(feeAmount).to.eq('1')
expect(amountOut).to.eq('1') // would be 2 if not capped
expect(sqrtQ).to.eq('417332158212080721273783715441581')
})
it('target price of 1 uses partial input amount', async () => {
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
BigNumber.from('2'),
BigNumber.from('1'),
'1',
'3915081100057732413702495386755767',
1
)
expect(amountIn).to.eq('39614081257132168796771975168')
expect(feeAmount).to.eq('39614120871253040049813')
expect(amountIn.add(feeAmount)).to.be.lte('3915081100057732413702495386755767')
expect(amountOut).to.eq('0')
expect(sqrtQ).to.eq('1')
})
it('entire input amount taken as fee', async () => {
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
'2413',
'79887613182836312',
'1985041575832132834610021537970',
'10',
1872
)
expect(amountIn).to.eq('0')
expect(feeAmount).to.eq('10')
expect(amountOut).to.eq('0')
expect(sqrtQ).to.eq('2413')
})
it('handles intermediate insufficient liquidity in zero for one exact output case', async () => {
const sqrtP = BigNumber.from('20282409603651670423947251286016')
const sqrtPTarget = sqrtP.mul(11).div(10)
const liquidity = 1024
// virtual reserves of one are only 4
// https://www.wolframalpha.com/input/?i=1024+%2F+%2820282409603651670423947251286016+%2F+2**96%29
const amountRemaining = -4
const feePips = 3000
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
sqrtP,
sqrtPTarget,
liquidity,
amountRemaining,
feePips
)
expect(amountOut).to.eq(0)
expect(sqrtQ).to.eq(sqrtPTarget)
expect(amountIn).to.eq(26215)
expect(feeAmount).to.eq(79)
})
it('handles intermediate insufficient liquidity in one for zero exact output case', async () => {
const sqrtP = BigNumber.from('20282409603651670423947251286016')
const sqrtPTarget = sqrtP.mul(9).div(10)
const liquidity = 1024
// virtual reserves of zero are only 262144
// https://www.wolframalpha.com/input/?i=1024+*+%2820282409603651670423947251286016+%2F+2**96%29
const amountRemaining = -263000
const feePips = 3000
const { amountIn, amountOut, sqrtQ, feeAmount } = await swapMath.computeSwapStep(
sqrtP,
sqrtPTarget,
liquidity,
amountRemaining,
feePips
)
expect(amountOut).to.eq(26214)
expect(sqrtQ).to.eq(sqrtPTarget)
expect(amountIn).to.eq(1)
expect(feeAmount).to.eq(1)
})
describe('gas', () => {
it('swap one for zero exact in capped', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(101, 100),
expandTo18Decimals(2),
expandTo18Decimals(1),
600
)
)
})
it('swap zero for one exact in capped', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(99, 100),
expandTo18Decimals(2),
expandTo18Decimals(1),
600
)
)
})
it('swap one for zero exact out capped', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(101, 100),
expandTo18Decimals(2),
expandTo18Decimals(1).mul(-1),
600
)
)
})
it('swap zero for one exact out capped', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(99, 100),
expandTo18Decimals(2),
expandTo18Decimals(1).mul(-1),
600
)
)
})
it('swap one for zero exact in partial', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(1010, 100),
expandTo18Decimals(2),
1000,
600
)
)
})
it('swap zero for one exact in partial', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(99, 1000),
expandTo18Decimals(2),
1000,
600
)
)
})
it('swap one for zero exact out partial', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(1010, 100),
expandTo18Decimals(2),
1000,
600
)
)
})
it('swap zero for one exact out partial', async () => {
await snapshotGasCost(
swapMath.getGasCostOfComputeSwapStep(
encodePriceSqrt(1, 1),
encodePriceSqrt(99, 1000),
expandTo18Decimals(2),
1000,
600
)
)
})
})
})
})