diff --git a/contracts/math/clamm.ral b/contracts/math/clamm.ral index a283bba..329295b 100644 --- a/contracts/math/clamm.ral +++ b/contracts/math/clamm.ral @@ -1,4 +1,4 @@ -Contract CLAMM() extends Log(){ +Contract CLAMM(uints: Uints) extends Log() { enum CLAMMError { InvalidTickIndex = 800 InvalidTickSpacing = 801 @@ -133,10 +133,17 @@ Contract CLAMM() extends Log(){ deltaSqrtPrice = sqrtPriceB - sqrtPriceA } + let mut result = U512 { lower: 0, higher: 0 } + if (roundingUp) { - return (mulUp(deltaSqrtPrice, liquidity, LiquidityScale) + almostOne(SqrtPriceScale)) / one(SqrtPriceScale) + result = uints.bigMulDiv256(deltaSqrtPrice, liquidity, one(LiquidityScale)) + result = uints.bigAdd512(result, uints.toU512(almostOne(SqrtPriceScale))) + result = uints.bigDiv(result, one(SqrtPriceScale), 1) + return uints.toU256(result) } else { - return mul(deltaSqrtPrice, liquidity, LiquidityScale) / one(SqrtPriceScale) + result = uints.bigMulDiv256(deltaSqrtPrice, liquidity, one(LiquidityScale)) + result = uints.bigDiv(result, one(SqrtPriceScale), 1) + return uints.toU256(result) } } diff --git a/contracts/math/uints.ral b/contracts/math/uints.ral index 12bbc79..f2c415b 100644 --- a/contracts/math/uints.ral +++ b/contracts/math/uints.ral @@ -42,10 +42,52 @@ Contract Uints () { } } + pub fn div(a: U512, b: U256, bDenominator: U256, up: Bool) -> U512 { + assert!(b > 0, ArithmeticErrors.NotPositiveDivisor) + + if (b == 1) { + return a + } + + // Calculate new higher + let newHigher = a.higher * bDenominator + let higher = newHigher / b + let higherRemainder = newHigher % b + // calculate higher remainder + let maxDiv = MaxU256 / b + let deltaHigherRemainder = higherRemainder * maxDiv + // Calculate lower + let newLower = a.lower * bDenominator + let lower = newLower / b + let mut deltaLower = deltaHigherRemainder + lower + + if (up) { + let lowerRemainder = newLower % b + + let higherDecimal = deltaHigherRemainder % b + if (higherDecimal + lowerRemainder != 0) { + deltaLower = deltaLower + 1 + } + } + + return U512 { + higher: higher, + lower: deltaLower + } + } + + pub fn bigDiv(a: U512, b: U256, bDenominator: U256) -> U512 { + return div(a, b, bDenominator, false) + } + + pub fn bigDivUp(a: U512, b: U256, bDenominator: U256) -> U512 { + return div(a, b, bDenominator, true) + } + pub fn bigAdd512(a: U512, b: U512) -> U512 { - let (aLowerBLower, overflow) = overflowingAdd(a.lower, b.lower) - let aHigherBHigherOverflow = wrappingAdd(wrappingAdd(a.higher, b.higher), overflow) - return U512 { lower: aLowerBLower, higher: aHigherBHigherOverflow } + let (aLowerB, overflow) = overflowingAdd(a.lower, b.lower) + let aHigherOverflow = wrappingAdd(wrappingAdd(a.higher, b.higher), overflow) + return U512 { lower: aLowerB, higher: aHigherOverflow } } pub fn bigDivWrapper(a: U512, b: U256, bDenominator: U256, up: Bool) -> U512 { @@ -127,8 +169,8 @@ Contract Uints () { pub fn bigMulDivUp256(a: U256, b: U256, bDenominator: U256) -> U512 { let mut result = bigMul256(a, b) - result = bigAdd512(result, U512 { higher: 0, lower: bDenominator - 1 }) - result = bigDiv512(result, bDenominator, 1) + result = bigAdd512(result, toU512(bDenominator - 1)) + result = bigDiv(result, bDenominator, 1) return result } diff --git a/src/utils.ts b/src/utils.ts index 2c3762a..13a3155 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -319,9 +319,13 @@ export async function deployPositionsCounter(signer: SignerProvider) { } export async function deployCLAMM(signer: SignerProvider) { + const uints = await deployUints(signer) + return await waitTxConfirmed( CLAMM.deploy(signer, { - initialFields: {} + initialFields: { + uints: uints.contractInstance.contractId + } }) ) } diff --git a/test/clamm.test.ts b/test/clamm.test.ts index dc171f1..f7e55e0 100644 --- a/test/clamm.test.ts +++ b/test/clamm.test.ts @@ -3,7 +3,7 @@ import { getSigner } from '@alephium/web3-test' import { PrivateKeyWallet } from '@alephium/web3-wallet' import { assert } from 'console' import { CLAMMInstance } from '../artifacts/ts' -import { deployCLAMM } from '../src/utils' +import { deployCLAMM, expectError } from '../src/utils' web3.setCurrentNodeProvider('http://127.0.0.1:22973') @@ -212,19 +212,143 @@ describe('math tests', () => { expect(resultUp).toEqual(70109n) expect(resultDown).toEqual(70108n) }) - test('get delta y', async () => { - const clamm = await deployCLAMM(sender) - const sqrtPriceA = 234878324943782000000000000n - const sqrtPriceB = 87854456421658000000000000n - const liquidity = 983983249092n - const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } } - const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } - const resultUp = (await clamm.contractInstance.methods.getDeltaY(paramsUp)).returns - const resultDown = (await clamm.contractInstance.methods.getDeltaY(paramsDown)).returns - // 144669023.842474597804911408 - expect(resultUp).toEqual(1446690239n) - expect(resultDown).toEqual(1446690238n) + + describe('get delta y', () => { + let clamm: CLAMMInstance + + beforeEach(async () => { + clamm = (await deployCLAMM(sender)).contractInstance + }) + + test('zero at zero liquidity', async () => { + const sqrtPriceA = 1_000000000000000000000000n + const sqrtPriceB = 1_000000000000000000000000n + const liquidity = 0n + + const params = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + const result = await clamm.methods.getDeltaY(params) + + expect(result.returns).toEqual(0n) + }) + + test('equal at equal liquidity', async () => { + const sqrtPriceA = 1_000000000000000000000000n + const sqrtPriceB = 2_000000000000000000000000n + const liquidity = 2_00000n + + const params = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + const result = await clamm.methods.getDeltaY(params) + + expect(result.returns).toEqual(2n) + }) + + test('big numbers', async () => { + const sqrtPriceA = 234_878324943782000000000000n + const sqrtPriceB = 87_854456421658000000000000n + const liquidity = 9839832_49092n + + const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } } + const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + const resultUp = await clamm.methods.getDeltaY(paramsUp) + const resultDown = await clamm.methods.getDeltaY(paramsDown) + + expect(resultUp.returns).toEqual(1446690239n) + expect(resultDown.returns).toEqual(1446690238n) + }) + + test('big', async () => { + const sqrtPriceA = 1_000000000000000000000000n + const sqrtPriceB = 2_000000000000000000000000n + const liquidity = (2n ** 64n - 1n) * 1_00000n + + const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } } + const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + const resultUp = await clamm.methods.getDeltaY(paramsUp) + const resultDown = await clamm.methods.getDeltaY(paramsDown) + + expect(resultUp.returns).toEqual(liquidity / 1_00000n) + expect(resultDown.returns).toEqual(liquidity / 1_00000n) + }) + + test('overflow', async () => { + const sqrtPriceA = 1_000000000000000000000000n + const sqrtPriceB = 2n ** 256n - 1n + const liquidity = 2n ** 256n - 1n + + const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } } + const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + await expectError(clamm.methods.getDeltaY(paramsUp)) + await expectError(clamm.methods.getDeltaY(paramsDown)) + }) + + test('huge liquidity', async () => { + const sqrtPriceA = 1_000000000000000000000000n + const sqrtPriceB = 1_000000000000000001000000n + const liquidity = 2n ** 256n - 1n + + const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } } + const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } } + const resultUp = await clamm.methods.getDeltaY(paramsUp) + const resultDown = await clamm.methods.getDeltaY(paramsDown) + + expect(resultUp.returns).toEqual(1157920892373161954235709850086879078532699846656405641n) + expect(resultDown.returns).toEqual(1157920892373161954235709850086879078532699846656405640n) + }) }) + + describe('get delta y - domain', () => { + let clamm: CLAMMInstance + const minSqrtPrice = 15258932000000000000n + const maxSqrtPrice = 65535_383934512647000000000000n + const minLiquidity = 1n + const maxLiquidity = 2n ** 256n - 1n + + beforeEach(async () => { + clamm = (await deployCLAMM(sender)).contractInstance + }) + + it('maximize delta sqrt price and liquidity', async () => { + const paramsUp = { + args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: minSqrtPrice, liquidity: maxLiquidity, roundingUp: true } + } + const paramsDown = { + args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: minSqrtPrice, liquidity: maxLiquidity, roundingUp: false } + } + const resultUp = await clamm.methods.getDeltaY(paramsUp) + const resultDown = await clamm.methods.getDeltaY(paramsDown) + + expect(resultUp.returns).toEqual(75884790229800029582010010030152469040784228171629896039591333116952600000001n) + expect(resultDown.returns).toEqual(75884790229800029582010010030152469040784228171629896039591333116952599999999n) + }) + + it('can be zero', async () => { + const params = { + args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: maxSqrtPrice - 1n, liquidity: minLiquidity, roundingUp: false } + } + const result = await clamm.methods.getDeltaY(params) + + expect(result.returns).toEqual(0n) + }) + + it('liquidity is zero', async () => { + const params = { + args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: minSqrtPrice, liquidity: 0n, roundingUp: true } + } + const result = await clamm.methods.getDeltaY(params) + + expect(result.returns).toEqual(0n) + }) + + it('all max', async () => { + const params = { + args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: maxSqrtPrice, liquidity: maxLiquidity, roundingUp: true } + } + const result = await clamm.methods.getDeltaY(params) + + expect(result.returns).toEqual(0n) + }) + }) + test('get next sqrt price x up', async () => { const clamm = await deployCLAMM(sender) {