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

Updated get delta x & added exhausting tests #45

Merged
merged 8 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
28 changes: 17 additions & 11 deletions contracts/math/clamm.ral
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Contract CLAMM() extends Log(){
Contract CLAMM(
uints: Uints
) extends Log(){
enum CLAMMError {
InvalidTickIndex = 800
InvalidTickSpacing = 801
Expand Down Expand Up @@ -106,17 +108,21 @@ Contract CLAMM() extends Log(){
deltaSqrtPrice = sqrtPriceB - sqrtPriceA
}

let nominator = mul(deltaSqrtPrice, liquidity, LiquidityScale)
if (roundingUp == true) {
return divToTokenUp(
nominator,
mul(sqrtPriceA, sqrtPriceB, SqrtPriceScale)
)
let nominator = uints.bigMulDiv256(deltaSqrtPrice, liquidity, one(LiquidityScale))
if (roundingUp) {
Sniezka1927 marked this conversation as resolved.
Show resolved Hide resolved
let denominator = mul(sqrtPriceA, sqrtPriceB, SqrtPriceScale)
Sniezka1927 marked this conversation as resolved.
Show resolved Hide resolved
let mut deltaXUp = uints.bigMul512(nominator, one(SqrtPriceScale))
Sniezka1927 marked this conversation as resolved.
Show resolved Hide resolved
deltaXUp = uints.bigAdd(deltaXUp, denominator - 1)
deltaXUp = uints.bigDiv512(deltaXUp, denominator, 1)
deltaXUp = uints.bigAdd(deltaXUp, almostOne(SqrtPriceScale))
deltaXUp = uints.bigDiv512(deltaXUp, one(SqrtPriceScale), 1)
return uints.toU256(deltaXUp)
} else {
return divToToken(
nominator,
mulUp(sqrtPriceA, sqrtPriceB, SqrtPriceScale)
)
let denominatorUp = mulUp(sqrtPriceA, sqrtPriceB, SqrtPriceScale)
let mut deltaX = uints.bigMul512(nominator, one(SqrtPriceScale))
deltaX = uints.bigDiv512(deltaX, denominatorUp, 1)
deltaX = uints.bigDiv512(deltaX, one(SqrtPriceScale), 1)
Sniezka1927 marked this conversation as resolved.
Show resolved Hide resolved
return uints.toU256(deltaX)
}
}

Expand Down
76 changes: 47 additions & 29 deletions contracts/math/uints.ral
Original file line number Diff line number Diff line change
Expand Up @@ -42,52 +42,70 @@ Contract Uints () {
}
}

pub fn bigAdd(a: U512, b: U256) -> U512 {
let (lower, overflow) = overflowingAdd(a.lower, b)
let higher = wrappingAdd(a.higher, overflow)
return U512 { lower: lower, higher: higher }
}

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 }
}

pub fn bigDivWrapper(a: U512, b: U256, bDenominator: U256, up: Bool) -> U512 {
assert!(b > 0, ArithmeticErrors.NotPositiveDivisor)
pub fn knuthDivision(dividend: U512, divisor: U256, divisorDenominator: U256) -> (U512, U256) {
let mut q = U512 { higher: 0, lower: 0 }
let mut r = 0

if (b == 1) {
return a
assert!(divisor != 0, ArithmeticErrors.NotPositiveDivisor)

if (dividend.higher == 0) {
return U512 { higher: 0, lower: dividend.lower / divisor }, dividend.lower % divisor
}

// 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
let mut uHigh = dividend.higher * divisorDenominator
let mut uLow = dividend.lower * divisorDenominator
let v = divisor

for (let mut j = 511; j > 0; j = j - 1) {
r = r << 1

if (((uHigh >> 255) & 1) != 0) {
r = r | 1
}
}
uHigh = uHigh << 1

return U512 {
higher: higher,
lower: deltaLower
if ((uLow >> 255) != 0) {
uHigh = uHigh | 1
}
uLow = uLow << 1
if (r >= v) {
r = r - v
if (j >= 256) {
q.higher = q.higher | (1 << (j - 256))
} else {
q.lower = q.lower | (1 << j)
}
}
}
return q, r
}


pub fn bigDiv512(a: U512, b: U256, bDenominator: U256) -> U512 {
return bigDivWrapper(a, b, bDenominator, false)
let (quotient, _) = knuthDivision(a, b, bDenominator)
return quotient
}

pub fn bigDivUp512(a: U512, b: U256, bDenominator: U256) -> U512 {
return bigDivWrapper(a, b, bDenominator, true)
let (quotient, remainder) = knuthDivision(a, b, bDenominator)

if (remainder != 0) {
return bigAdd512(quotient, U512 { higher: 0, lower: 1 })
} else {
return quotient
}
}

pub fn bigMul512(a: U512, b: U256) -> U512 {
Expand Down Expand Up @@ -128,7 +146,7 @@ Contract Uints () {
let mut result = bigMul256(a, b)

result = bigAdd512(result, U512 { higher: 0, lower: bDenominator - 1 })
result = bigDiv512(result, bDenominator, 1)
result = bigDivUp512(result, bDenominator, 1)

return result
}
Expand Down
6 changes: 5 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
})
)
}
Expand Down
198 changes: 185 additions & 13 deletions test/clamm.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ONE_ALPH, web3 } from '@alephium/web3'
import { DeployContractResult, ONE_ALPH, web3 } from '@alephium/web3'
import { getSigner } from '@alephium/web3-test'
import { PrivateKeyWallet } from '@alephium/web3-wallet'
import { assert } from 'console'
Expand Down Expand Up @@ -199,18 +199,190 @@ describe('math tests', () => {
expect(sqrtPrice).toEqual(998501199320000000000000n)
}
})
test('get delta x', 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.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
// 7010.8199533068819376891841727789301497024557314488455622925765280
expect(resultUp).toEqual(70109n)
expect(resultDown).toEqual(70108n)
describe('get delta x', () => {
let clamm: DeployContractResult<CLAMMInstance>

beforeAll(async () => {
clamm = await deployCLAMM(sender)
})
test('zero at zero liquidity', async () => {
const sqrtPriceA = 1n * 10n ** 24n
const sqrtPriceB = 2n * 10n ** 24n
const liquidity = 0n
const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } }
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultDown).toEqual(0n)
})
test('equal at equal liquidity', async () => {
const sqrtPriceA = 1n * 10n ** 24n
const sqrtPriceB = 2n * 10n ** 24n
const liquidity = 2n * 10n ** 5n
const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } }
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultDown).toEqual(1n)
})
test('complex', async () => {
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.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(70109n)
expect(resultDown).toEqual(70108n)
})
test('big', async () => {
const sqrtPriceA = 1n * 10n ** 24n
const sqrtPriceB = 5n * 10n ** 23n
const liquidity = (2n ** 64n - 1n) * 10n ** 5n
const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } }
const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(2n ** 64n - 1n)
expect(resultDown).toEqual(2n ** 64n - 1n)
})
test('shouldnt overflow in intermediate opeartions', async () => {
const sqrtPriceA = 1n * 10n ** 24n
const sqrtPriceB = 5n * 10n ** 23n
const liquidity = (1n << 256n) - 1n
const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } }
const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } }
await clamm.contractInstance.methods.getDeltaX(paramsUp)
await clamm.contractInstance.methods.getDeltaX(paramsDown)
})
test('huge liquididty', async () => {
const sqrtPriceA = 1n * 10n ** 24n
const sqrtPriceB = 1n * 10n ** 24n + 1000000n
const liquidity = 2n << 80n
const paramsUp = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: true } }
const paramsDown = { args: { sqrtPriceA, sqrtPriceB, liquidity, roundingUp: false } }
await clamm.contractInstance.methods.getDeltaX(paramsUp)
await clamm.contractInstance.methods.getDeltaX(paramsDown)
})
})
describe('get delta x - domain', () => {
let clamm: DeployContractResult<CLAMMInstance>
const maxSqrtPrice = 65535383934512647000000000000n
const minSqrtPrice = 15258932000000000000n
const almostMinSqrtPrice = 15259695000000000000n
const maxLiquidity = (1n << 256n) - 1n
const minLiquidity = 1n

beforeAll(async () => {
clamm = await deployCLAMM(sender)
})
test('maximalize delta sqrt price and liquidity', async () => {
const params = {
sqrtPriceA: maxSqrtPrice,
sqrtPriceB: minSqrtPrice,
liquidity: maxLiquidity
}
const paramsUp = { args: { ...params, roundingUp: true } }
const paramsDown = { args: { ...params, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
// expected: 75884792730156830614567103553061795263351065677581979504561495713443442818879n
// received: 75884792730156830614567103553061795263351065677581979504561495713443442818878n
// Missing +1n
expect(resultUp).toEqual(75884792730156830614567103553061795263351065677581979504561495713443442818878n)
expect(resultDown).toEqual(75884792730156830614567103553061795263351065677581979504561495713443442818878n)
})
test('maximalize delta sqrt price and minimalize liquidity', async () => {
const params = {
sqrtPriceA: maxSqrtPrice,
sqrtPriceB: minSqrtPrice,
liquidity: minLiquidity
}
const paramsUp = { args: { ...params, roundingUp: true } }
const paramsDown = { args: { ...params, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(1n)
expect(resultDown).toEqual(0n)
})
test('minimize denominator on maximize liquidity which fit into token amounts', async () => {
const params = {
sqrtPriceA: minSqrtPrice,
sqrtPriceB: almostMinSqrtPrice,
liquidity: maxLiquidity
}
const paramsUp = { args: { ...params, roundingUp: true } }
const paramsDown = { args: { ...params, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(3794315473971847510172532341754979462199874072217062973965311338137066234n)
// expected: 3794315473971847510172532341754979462199874072217062973965311338137066233n
// received: 3794315473971847510172532341754979462199874072217062973965311338137066232n
// Missing +1n
expect(resultDown).toEqual(3794315473971847510172532341754979462199874072217062973965311338137066232n)
})
test('minimize denominator on minimize liquidity which fit into token amounts', async () => {
const params = {
sqrtPriceA: minSqrtPrice,
sqrtPriceB: almostMinSqrtPrice,
liquidity: minLiquidity
}
const paramsUp = { args: { ...params, roundingUp: true } }
const paramsDown = { args: { ...params, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(1n)
expect(resultDown).toEqual(0n)
})
test('delta price limited by search range on max liquidity', async () => {
const searchLimit = 256n
const tickSpacing = 100n
const maxSearchLimit = 221818n - searchLimit * tickSpacing
const minSearchSqrtPrice = (
await clamm.contractInstance.methods.calculateSqrtPrice({
args: { tickIndex: maxSearchLimit }
})
).returns

const params = {
sqrtPriceA: maxSqrtPrice,
sqrtPriceB: minSearchSqrtPrice,
liquidity: maxLiquidity
}
const paramsUp = { args: { ...params, roundingUp: true } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
// Expected: 45875017378130362421757891862614875858481775310156442203847653871247n
// Received: 45875017378130362421757891862614875858481775310156442203847653871246n
// Missing +1n
expect(resultUp).toEqual(45875017378130362421757891862614875858481775310156442203847653871246n)
})
test('minimal price diffrence', async () => {
const almostMaxSqrtPrice = maxSqrtPrice - 1n * 10n ** 24n
const almostMinSqrtPrice = minSqrtPrice + 1n * 10n ** 24n
const paramsUpperBound = {
args: { sqrtPriceA: maxSqrtPrice, sqrtPriceB: almostMaxSqrtPrice, liquidity: maxLiquidity, roundingUp: true }
}
const paramsBottomBound = {
args: { sqrtPriceA: minSqrtPrice, sqrtPriceB: almostMinSqrtPrice, liquidity: maxLiquidity, roundingUp: true }
}
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUpperBound)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsBottomBound)).returns
// expected: 269608649375997235557394191156352599353486422139915865816324471n
// received: 269608649375997235557394191156352599353486422139915865816324470n
// Missing +1n
expect(resultUp).toEqual(269608649375997235557394191156352599353486422139915865816324470n)
expect(resultDown).toEqual(75883634844601460750582416171430603974060896681619645705711819135499453546638n)
})
test('zero liquidity', async () => {
const params = {
sqrtPriceA: maxSqrtPrice,
sqrtPriceB: minSqrtPrice,
liquidity: 0n
}
const paramsUp = { args: { ...params, roundingUp: true } }
const paramsDown = { args: { ...params, roundingUp: false } }
const resultUp = (await clamm.contractInstance.methods.getDeltaX(paramsUp)).returns
const resultDown = (await clamm.contractInstance.methods.getDeltaX(paramsDown)).returns
expect(resultUp).toEqual(0n)
expect(resultDown).toEqual(0n)
})
})
test('get delta y', async () => {
const clamm = await deployCLAMM(sender)
Expand Down
Loading