Skip to content

Commit

Permalink
add buy tests and tick bitmap constants by chain
Browse files Browse the repository at this point in the history
  • Loading branch information
aburkut committed Feb 13, 2025
1 parent 908efc1 commit 035578e
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/dex/pancakeswap-v3/pancakeswap-v3-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export class PancakeSwapV3EventPool extends StatefulEventSubscriber<PoolState> {
const requestedRange = this.getBitmapRangeToRequest();

return {
networkId: this.dexHelper.config.data.network,
pool: _state.pool,
blockTimestamp: bigIntify(_state.blockTimestamp),
slot0: {
Expand Down
10 changes: 10 additions & 0 deletions src/dex/uniswap-v3/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Network } from '../../constants';

export const UNISWAPV3_TICK_GAS_COST = 24_000; // Ceiled
export const UNISWAPV3_TICK_BASE_OVERHEAD = 75_000;
export const UNISWAPV3_POOL_SEARCH_OVERHEAD = 10_000;
Expand All @@ -8,6 +10,14 @@ export const TICK_BITMAP_TO_USE = 4n;
// This is used to check if the state is still valid.
export const TICK_BITMAP_BUFFER = 8n;

export const TICK_BITMAP_TO_USE_BY_CHAIN: Record<number, bigint> = {
[Network.MAINNET]: 8n,
};

export const TICK_BITMAP_BUFFER_BY_CHAIN: Record<number, bigint> = {
[Network.MAINNET]: 16n,
};

export const MAX_PRICING_COMPUTATION_STEPS_ALLOWED = 128;

export const UNISWAPV3_SUBGRAPH_URL =
Expand Down
38 changes: 29 additions & 9 deletions src/dex/uniswap-v3/contract-math/TickBitMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { DeepReadonly } from 'ts-essentials';
import {
OUT_OF_RANGE_ERROR_POSTFIX,
TICK_BITMAP_BUFFER,
TICK_BITMAP_BUFFER_BY_CHAIN,
TICK_BITMAP_TO_USE,
TICK_BITMAP_TO_USE_BY_CHAIN,
} from '../constants';

function isWordPosOut(
Expand All @@ -15,18 +17,24 @@ function isWordPosOut(
// For pricing we use wider range to check price impact. If function called from event
// it must always be within buffer
isPriceQuery: boolean,
networkId: number,
) {
let lowerTickBitmapLimit;
let upperTickBitmapLimit;

const tickBitMapToUse =
TICK_BITMAP_TO_USE_BY_CHAIN[networkId] ?? TICK_BITMAP_TO_USE;
const tickBitMapBuffer =
TICK_BITMAP_BUFFER_BY_CHAIN[networkId] ?? TICK_BITMAP_BUFFER;

if (isPriceQuery) {
lowerTickBitmapLimit =
startTickBitmap - (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE);
startTickBitmap - (tickBitMapBuffer + tickBitMapToUse);
upperTickBitmapLimit =
startTickBitmap + (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE);
startTickBitmap + (tickBitMapBuffer + tickBitMapToUse);
} else {
lowerTickBitmapLimit = startTickBitmap - TICK_BITMAP_BUFFER;
upperTickBitmapLimit = startTickBitmap + TICK_BITMAP_BUFFER;
lowerTickBitmapLimit = startTickBitmap - tickBitMapBuffer;
upperTickBitmapLimit = startTickBitmap + tickBitMapBuffer;
}

_require(
Expand All @@ -43,7 +51,7 @@ export class TickBitMap {
}

static flipTick(
state: Pick<PoolState, 'startTickBitmap' | 'tickBitmap'>,
state: Pick<PoolState, 'startTickBitmap' | 'tickBitmap' | 'networkId'>,
tick: bigint,
tickSpacing: bigint,
) {
Expand All @@ -58,7 +66,7 @@ export class TickBitMap {

// flipTick is used only in _updatePosition which is always state changing event
// Therefore it is never used in price query
isWordPosOut(wordPos, state.startTickBitmap, false);
isWordPosOut(wordPos, state.startTickBitmap, false, state.networkId);

const stringWordPos = wordPos.toString();
if (state.tickBitmap[stringWordPos] === undefined) {
Expand All @@ -69,7 +77,9 @@ export class TickBitMap {
}

static nextInitializedTickWithinOneWord(
state: DeepReadonly<Pick<PoolState, 'startTickBitmap' | 'tickBitmap'>>,
state: DeepReadonly<
Pick<PoolState, 'startTickBitmap' | 'tickBitmap' | 'networkId'>
>,
tick: bigint,
tickSpacing: bigint,
lte: boolean,
Expand All @@ -85,7 +95,12 @@ export class TickBitMap {
const [wordPos, bitPos] = TickBitMap.position(compressed);
const mask = (1n << bitPos) - 1n + (1n << bitPos);

isWordPosOut(wordPos, state.startTickBitmap, isPriceQuery);
isWordPosOut(
wordPos,
state.startTickBitmap,
isPriceQuery,
state.networkId,
);
let tickBitmapValue = state.tickBitmap[wordPos.toString()];
tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue;

Expand All @@ -102,7 +117,12 @@ export class TickBitMap {
const [wordPos, bitPos] = TickBitMap.position(compressed + 1n);
const mask = ~((1n << bitPos) - 1n);

isWordPosOut(wordPos, state.startTickBitmap, isPriceQuery);
isWordPosOut(
wordPos,
state.startTickBitmap,
isPriceQuery,
state.networkId,
);
let tickBitmapValue = state.tickBitmap[wordPos.toString()];
tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue;

Expand Down
1 change: 1 addition & 0 deletions src/dex/uniswap-v3/forks/ramses-v2/ramses-v2-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class RamsesV2EventPool extends UniswapV3EventPool {
const requestedRange = this.getBitmapRangeToRequest();

return {
networkId: this.dexHelper.config.data.network,
pool: _state.pool,
blockTimestamp: bigIntify(_state.blockTimestamp),
slot0: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class VelodromeSlipstreamEventPool extends UniswapV3EventPool {
const requestedRange = this.getBitmapRangeToRequest();

return {
networkId: this.dexHelper.config.data.network,
pool: _state.pool,
fee,
blockTimestamp: bigIntify(_state.blockTimestamp),
Expand Down
1 change: 1 addition & 0 deletions src/dex/uniswap-v3/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type Slot0 = {
};

export type PoolState = {
networkId: number;
pool: string;
blockTimestamp: bigint;
tickSpacing: bigint;
Expand Down
159 changes: 149 additions & 10 deletions src/dex/uniswap-v3/uniswap-v3-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ async function checkOnChainPricing(
fee,
);

console.log('blockNumber: ', blockNumber);
console.log('readerCallData: ', readerCallData);

let readerResult;
try {
readerResult = (
Expand Down Expand Up @@ -174,16 +177,6 @@ describe('UniswapV3', () => {
8n * BI_POWS[18],
9n * BI_POWS[18],
10n * BI_POWS[18],
11n * BI_POWS[18],
12n * BI_POWS[18],
13n * BI_POWS[18],
14n * BI_POWS[18],
15n * BI_POWS[18],
16n * BI_POWS[18],
17n * BI_POWS[18],
18n * BI_POWS[18],
19n * BI_POWS[18],
20n * BI_POWS[18],
];

const pools = await uniswapV3.getPoolIdentifiers(
Expand Down Expand Up @@ -246,6 +239,77 @@ describe('UniswapV3', () => {
expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
});

it('DAI -> USDC getPoolIdentifiers and getPricesVolume BUY', async () => {
const amounts = [
0n,
2n * BI_POWS[18],
3n * BI_POWS[18],
4n * BI_POWS[18],
5n * BI_POWS[18],
];

const pools = await uniswapV3.getPoolIdentifiers(
TokenA,
TokenB,
SwapSide.BUY,
blockNumber,
);
console.log(
`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `,
pools,
);

expect(pools.length).toBeGreaterThan(0);

const prices = await uniswapV3.getPricesVolume(
TokenA,
TokenB,
amounts,
SwapSide.BUY,
blockNumber,
pools,
);

console.log('poolPrices: ');

const poolPrices = prices?.filter(
price =>
price.poolIdentifier &&
price.poolIdentifier.toLowerCase() ===
'UniswapV3_0x6b175474e89094c44da98b954eedeac495271d0f_0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48_10000'.toLowerCase(),
);

console.log(
`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `,
poolPrices,
);

expect(poolPrices).not.toBeNull();
// checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);

let falseChecksCounter = 0;
await Promise.all(
poolPrices!.map(async price => {
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
const res = await checkOnChainPricing(
dexHelper,
uniswapV3,
'quoteExactOutputSingle',
blockNumber,
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
price.prices,
TokenA.address,
TokenB.address,
fee,
amounts,
);
if (res === false) falseChecksCounter++;
}),
);

expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
});

it('USDC -> DAI getPoolIdentifiers and getPricesVolume SELL', async () => {
const amounts = [
0n,
Expand Down Expand Up @@ -314,6 +378,81 @@ describe('UniswapV3', () => {

expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
});

it('USDC -> DAI getPoolIdentifiers and getPricesVolume BUY', async () => {
const amounts = [
0n,
1n * BI_POWS[17],
5n * BI_POWS[17],
5n * BI_POWS[17],
6n * BI_POWS[17],
7n * BI_POWS[17],
8n * BI_POWS[17],
9n * BI_POWS[17],
1n * BI_POWS[18],
2n * BI_POWS[18],
3n * BI_POWS[18],
];

const pools = await uniswapV3.getPoolIdentifiers(
TokenB,
TokenA,
SwapSide.BUY,
blockNumber,
);
console.log(
`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `,
pools,
);

expect(pools.length).toBeGreaterThan(0);

const prices = await uniswapV3.getPricesVolume(
TokenB,
TokenA,
amounts,
SwapSide.BUY,
blockNumber,
pools,
);

const poolPrices = prices?.filter(
price =>
price.poolIdentifier &&
price.poolIdentifier.toLowerCase() ===
'UniswapV3_0x6b175474e89094c44da98b954eedeac495271d0f_0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48_10000'.toLowerCase(),
);

console.log(
`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `,
poolPrices,
);

expect(poolPrices).not.toBeNull();
// checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);

let falseChecksCounter = 0;
await Promise.all(
poolPrices!.map(async price => {
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
const res = await checkOnChainPricing(
dexHelper,
uniswapV3,
'quoteExactOutputSingle',
blockNumber,
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
price.prices,
TokenB.address,
TokenA.address,
fee,
amounts,
);
if (res === false) falseChecksCounter++;
}),
);

expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
});
});
});

Expand Down
12 changes: 11 additions & 1 deletion src/dex/uniswap-v3/uniswap-v3-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
OUT_OF_RANGE_ERROR_POSTFIX,
TICK_BITMAP_BUFFER,
TICK_BITMAP_TO_USE,
TICK_BITMAP_TO_USE_BY_CHAIN,
TICK_BITMAP_BUFFER_BY_CHAIN,
} from './constants';
import { TickBitMap } from './contract-math/TickBitMap';
import { uint256ToBigInt } from '../../lib/decoders';
Expand Down Expand Up @@ -251,7 +253,14 @@ export class UniswapV3EventPool extends StatefulEventSubscriber<PoolState> {
}

getBitmapRangeToRequest() {
return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER;
const networkId = this.dexHelper.config.data.network;

const tickBitMapToUse =
TICK_BITMAP_TO_USE_BY_CHAIN[networkId] ?? TICK_BITMAP_TO_USE;
const tickBitMapBuffer =
TICK_BITMAP_BUFFER_BY_CHAIN[networkId] ?? TICK_BITMAP_BUFFER;

return tickBitMapToUse + tickBitMapBuffer;
}

async checkState(
Expand Down Expand Up @@ -330,6 +339,7 @@ export class UniswapV3EventPool extends StatefulEventSubscriber<PoolState> {
const requestedRange = this.getBitmapRangeToRequest();

return {
networkId: this.dexHelper.config.data.network,
pool: _state.pool,
blockTimestamp: bigIntify(_state.blockTimestamp),
slot0: {
Expand Down
4 changes: 4 additions & 0 deletions tests/constants-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ export const Tokens: {
addBalance: _balancesFn,
addAllowance: _allowancesFn,
},
MLN: {
address: '0xa9f37d84c856fda3812ad0519dad44fa0a3fe207',
decimals: 11,
},
TEL: {
address: '0xdf7837de1f2fa4631d716cf2502f8b230f1dcc32',
decimals: 2,
Expand Down

0 comments on commit 035578e

Please sign in to comment.