Skip to content

Commit

Permalink
Merge pull request #109 from invariant-labs/get-tickmap
Browse files Browse the repository at this point in the history
Get tickmap
  • Loading branch information
Sniezka1927 authored Jul 19, 2024
2 parents 70f4ce2 + 90fb3da commit 895bb58
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 7 deletions.
3 changes: 2 additions & 1 deletion contracts/collections/tickmap.ral
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ Abstract Contract Tickmap() extends Decimal(), BatchHelper() {
0, 0, 0, 0
]
}

bitmap.insert!(originalCaller, id, batch)
}
}
Expand All @@ -271,7 +272,7 @@ Abstract Contract Tickmap() extends Decimal(), BatchHelper() {
@using(checkExternalCaller = false)
pub fn getMaxChunk(tickSpacing: U256) -> (U256) {
let maxTick = getMaxTick(tickSpacing)
let maxBitmapIndex = maxTick + GlobalMaxTick / toI256!(tickSpacing)
let maxBitmapIndex = (maxTick + GlobalMaxTick) / toI256!(tickSpacing)
let maxChunkIndex = toU256!(maxBitmapIndex) / ChunkSize
return maxChunkIndex
}
Expand Down
28 changes: 28 additions & 0 deletions contracts/invariant.ral
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,34 @@ Contract Invariant(
}
}

fn getSingleBatch(poolKey: PoolKey, index: U256) -> ByteVec {
let mut batch = #
let key = poolKeyBytes(poolKey) ++ toByteVec!(index)
if (bitmap.contains!(key)) {
let tickmapBatch = bitmap[key]
for (let mut i = 0; i < ChunksPerBatch; i = i + 1) {
let offset = index * ChunksPerBatch + i
batch = batch ++ toByteVec!(offset) ++ b`break` ++ toByteVec!(tickmapBatch.chunks[i]) ++ b`break`
}
}
return batch
}


pub fn getTickmapSlice(poolKey: PoolKey, lowerBatch: U256, upperBatch: U256, xToY: Bool) -> ByteVec {
let mut slice = #
if (xToY) {
for(let mut batchIndex = lowerBatch; batchIndex < upperBatch; batchIndex = batchIndex + 1) {
slice = slice ++ getSingleBatch(poolKey, batchIndex)
}
} else {
for(batchIndex = upperBatch; batchIndex >= lowerBatch; batchIndex = batchIndex - 1) {
slice = slice ++ getSingleBatch(poolKey, batchIndex)
}
}
return slice
}

pub fn getAllPoolsForPair(token0: ByteVec, token1: ByteVec) -> ByteVec {
let mut matchingPools = b``

Expand Down
5 changes: 4 additions & 1 deletion src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const {
// MinSqrtPrice,
MaxFeeTiers,
SearchRange,
InvariantError
InvariantError,
ChunksPerBatch,
ChunkSize
} = Invariant.consts

export const { CLAMMError, DecimalError, WordSize, ArithmeticError } = CLAMM.consts
Expand All @@ -26,6 +28,7 @@ export const MaxU256 =

export const MaxSqrtPrice = 65535383934512647000000000000n
export const MinSqrtPrice = 15258932000000000000n
export const MAX_BATCHES_QUERIED = 18n

export enum VMError {
ArithmeticError = 'ArithmeticError',
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ export {
SearchRange,
InvariantError,
MaxU256
} from './consts.js'
} from './consts'
33 changes: 31 additions & 2 deletions src/invariant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ import {
deployReserve,
MAP_ENTRY_DEPOSIT,
waitTxConfirmed,
constructTickmap,
getMaxBatch,
decodePools,
decodePoolKeys,
getNodeUrl,
signAndSend
} from './utils'
import { MAX_BATCHES_QUERIED } from './consts'
import {
Address,
ALPH_TOKEN_ID,
Expand Down Expand Up @@ -496,8 +499,34 @@ export class Invariant {
}

// async getPositionTicks() {}
// async getRawTickmap() {}
// async getFullTickmap() {}
async getRawTickmap(
poolKey: PoolKey,
lowerBatch: bigint,
upperBatch: bigint,
xToY: boolean
): Promise<[bigint, bigint][]> {
const response = await this.instance.view.getTickmapSlice({
args: { poolKey, lowerBatch, upperBatch, xToY }
})

return constructTickmap(response.returns)
}

async getFullTickmap(poolKey: PoolKey) {
const promises: Promise<[bigint, bigint][]>[] = []
const maxBatch = await getMaxBatch(poolKey.feeTier.tickSpacing)
let currentBatch = 0n

while (currentBatch <= maxBatch) {
let nextBatch = currentBatch + MAX_BATCHES_QUERIED
promises.push(this.getRawTickmap(poolKey, currentBatch, nextBatch, true))
currentBatch += MAX_BATCHES_QUERIED
}

const fullResult: [bigint, bigint][] = (await Promise.all(promises)).flat(1)
const storedTickmap = new Map<bigint, bigint>(fullResult)
return { bitmap: storedTickmap }
}
// async getLiquidityTicks() {}
// async getAllLiquidityTicks() {}
// async getUserPositionAmount() {}
Expand Down
23 changes: 22 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
import { CLAMM, Invariant, InvariantInstance, Reserve, Utils } from '../artifacts/ts'
import { TokenFaucet } from '../artifacts/ts/TokenFaucet'
import { FeeTier, FeeTiers, Pool, PoolKey, Position, Tick } from '../artifacts/ts/types'
import { MaxFeeTiers } from './consts'
import { ChunkSize, ChunksPerBatch, MaxFeeTiers } from './consts'
import { getMaxTick, getMinTick } from './math'
import { Network } from './network'

const BREAK_BYTES = '627265616b'
Expand Down Expand Up @@ -258,6 +259,26 @@ export const newFeeTier = async (fee: bigint, tickSpacing: bigint): Promise<FeeT
})
).returns
}

export const constructTickmap = async (string: string): Promise<[bigint, bigint][]> => {
const parts = string.split('627265616b')
const chunks: any[] = []

for (let i = 0; i < parts.length - 1; i += 2) {
chunks.push([decodeU256(parts[i]), decodeU256(parts[i + 1])])
}

return chunks
}

export const getMaxBatch = async (tickSpacing: bigint) => {
const maxTick = await getMaxTick(tickSpacing)
const minTick = await getMinTick(tickSpacing)
const ticksAmount = -minTick + maxTick + 1n
const lastBatch = ticksAmount / (ChunkSize * ChunksPerBatch)
return lastBatch
}

export function getNodeUrl(network: Network) {
if (network === Network.Local || network === Network.Devnet) {
return 'http://127.0.0.1:22973'
Expand Down
225 changes: 225 additions & 0 deletions test/sdk/e2e/get-tickmap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { ONE_ALPH, web3 } from '@alephium/web3'
import { getSigner } from '@alephium/web3-test'
import { Invariant } from '../../../src/invariant'
import { Network } from '../../../src/network'
import { PrivateKeyWallet } from '@alephium/web3-wallet'
import { getBasicFeeTickSpacing } from '../../../src/snippets'
import { TokenFaucetInstance } from '../../../artifacts/ts'
import { initTokensXY, withdrawTokens } from '../../../src/testUtils'
import { FeeTier, PoolKey } from '../../../artifacts/ts/types'
import { balanceOf, newFeeTier, newPoolKey } from '../../../src/utils'
import { GlobalMaxTick, GlobalMinTick } from '../../../src'
import { ChunkSize, ChunksPerBatch } from '../../../src/consts'
import { getMaxChunk, getMaxTick, getMinTick, toLiquidity } from '../../../src/math'

web3.setCurrentNodeProvider('http://127.0.0.1:22973')

let invariant: Invariant
let deployer: PrivateKeyWallet
let positionOwner: PrivateKeyWallet
let tokenX: TokenFaucetInstance
let tokenY: TokenFaucetInstance
let feeTier: FeeTier
let poolKey: PoolKey

describe('query tickmap tests', () => {
const initialFee = 0n
const [fee] = getBasicFeeTickSpacing()
const tickSpacing = 1n
const initSqrtPrice = 10n ** 24n
const supply = 10n ** 10n
const lowerTickIndex = GlobalMinTick
const upperTickIndex = GlobalMaxTick
const ticks = [-221818n, -221817n, -58n, 5n, 221817n, 221818n]

beforeEach(async () => {
deployer = await getSigner(ONE_ALPH * 1000n, 0)
positionOwner = await getSigner(ONE_ALPH * 1000n, 0)
invariant = await Invariant.deploy(deployer, Network.Local, initialFee)
;[tokenX, tokenY] = await initTokensXY(deployer, supply)

feeTier = await newFeeTier(fee, tickSpacing)
poolKey = await newPoolKey(tokenX.contractId, tokenY.contractId, feeTier)

await invariant.addFeeTier(deployer, feeTier)
await invariant.createPool(
deployer,
tokenX.contractId,
tokenY.contractId,
feeTier,
initSqrtPrice
)
await withdrawTokens(positionOwner, [tokenX, supply], [tokenY, supply])
})

test('get all initialized batches', async () => {
const liquidityDelta = toLiquidity(10n)
const { sqrtPrice } = await invariant.getPool(poolKey)
await invariant.createPosition(
positionOwner,
poolKey,
lowerTickIndex,
upperTickIndex,
liquidityDelta,
supply,
supply,
sqrtPrice,
sqrtPrice
)
const batchSize = ChunkSize * ChunksPerBatch
const pool = await invariant.getPool(poolKey)
for (let i = GlobalMinTick; i <= GlobalMaxTick; i += batchSize) {
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
i,
i + 1n,
10n,
approveX,
approveY,
pool.sqrtPrice,
pool.sqrtPrice
)
}
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
i + 2n,
i + 3n,
10n,
approveX,
approveY,
pool.sqrtPrice,
pool.sqrtPrice
)
}
}

const tikcmap = await invariant.getFullTickmap(poolKey)
const maxChunk = await getMaxChunk(tickSpacing)
const lastChunkInBatch =
BigInt(Math.ceil(Number(maxChunk) / Number(ChunksPerBatch))) * ChunksPerBatch
expect(tikcmap.bitmap.size).toBe(Number(lastChunkInBatch))
}, 1000000)

test('get tickmap', async () => {
const { sqrtPrice } = await invariant.getPool(poolKey)
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
ticks[2],
ticks[3],
10n,
approveX,
approveY,
sqrtPrice,
sqrtPrice
)

const tickmap = await invariant.getFullTickmap(poolKey)
for (const [chunkIndex, value] of tickmap.bitmap.entries()) {
if (chunkIndex === 866n) {
expect(value).toBe(0x80000000000000010000000000000000n)
} else {
expect(value).toBe(0n)
}
}
})
test('get tickmap edge tick initialized on tick spacing equal 1', async () => {
const { sqrtPrice } = await invariant.getPool(poolKey)
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
ticks[0],
ticks[1],
10n,
approveX,
approveY,
sqrtPrice,
sqrtPrice
)
}
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
ticks[4],
ticks[5],
10n,
approveX,
approveY,
sqrtPrice,
sqrtPrice
)
}
const tickmap = await invariant.getFullTickmap(poolKey)
const maxChunk = await getMaxChunk(tickSpacing)
expect(tickmap.bitmap.get(0n)).toBe(0b11n)
expect(tickmap.bitmap.get(maxChunk)).toBe(
0x18000000000000000000000000000000000000000000000000000000000000n
)
})
test('get tickmap edge tick initialized on tick spacing equal 100', async () => {
const tickSpacing = 100n
feeTier = await newFeeTier(fee, tickSpacing)
poolKey = await newPoolKey(tokenX.contractId, tokenY.contractId, feeTier)

await invariant.addFeeTier(deployer, feeTier)
await invariant.createPool(
deployer,
tokenX.contractId,
tokenY.contractId,
feeTier,
initSqrtPrice
)
const { sqrtPrice } = await invariant.getPool(poolKey)
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
await getMinTick(tickSpacing),
(await getMinTick(tickSpacing)) + tickSpacing,
10n,
approveX,
approveY,
sqrtPrice,
sqrtPrice
)
}
{
const approveX = await balanceOf(tokenX.contractId, positionOwner.address)
const approveY = await balanceOf(tokenY.contractId, positionOwner.address)
await invariant.createPosition(
positionOwner,
poolKey,
(await getMaxTick(tickSpacing)) - tickSpacing,
await getMaxTick(tickSpacing),
10n,
approveX,
approveY,
sqrtPrice,
sqrtPrice
)
}
const tickmap = await invariant.getFullTickmap(poolKey)
const maxChunk = await getMaxChunk(tickSpacing)

expect(tickmap.bitmap.get(0n)).toBe(0b11n)
expect(tickmap.bitmap.get(maxChunk)).toBe(0x1800000000000000000000n)
})
})
2 changes: 1 addition & 1 deletion test/sdk/e2e/query-on-pair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,5 @@ describe('query on token pair tests', () => {

const queriedPools = await invariant.getAllPoolsForPair(tokenX.contractId, tokenY.contractId)
expect(queriedPools.length).toBe(32)
})
}, 30000)
})

0 comments on commit 895bb58

Please sign in to comment.