CenturionDEX

State variables

Last modified:

To keep notation consistent with the previous sections, we continue to denote the two pool assets by XX and YY, even though smart-contract implementations often refer to them as token0 and token1.

We will be interested in the variables listed in the table below. The entries marked by TT are tick-indexed variables; that is, a separate value is stored for each initialized tick.

Table. State variables used by the CenturionDEX v3 pool contract

VariableNotationType
Current tick indexici_c
Current square-root pricep\sqrt{p}
Total active liquidityLtotL_{\mathrm{tot}}
Net liquidity at tick iiΔL(i)\Delta L(i)TT
Gross liquidity at tick iiLg(i)L_g(i)TT
Global fee growth in tokens XX and YYfgX,  fgYf_g^X,\; f_g^Y
Fee growth outside tick ii in tokens XX and YYfoX(i),  foY(i)f_o^X(i),\; f_o^Y(i)TT

Current tick index

The current tick index ici_c is the integer tick index associated with the current market price. Since the price grid is defined by

p(i)=1.0001i,p(i)=1.0001^i,

the current tick index is given by

ic=log1.0001(p).i_c=\left\lfloor \log_{1.0001}(p)\right\rfloor.

Thus, ici_c identifies the greatest tick whose associated price does not exceed the current price pp.

Square root of the current price and total active liquidity

Rather than tracking the real reserves of the pool directly, the CenturionDEX v3 contract tracks the current square-root price p\sqrt{p} together with the total active liquidity LtotL_{\mathrm{tot}}. This is sufficient because the corresponding virtual reserves satisfy

xv=Ltotp,yv=Ltotp.x_v=\frac{L_{\mathrm{tot}}}{\sqrt{p}}, \qquad y_v=L_{\mathrm{tot}}\sqrt{p}.

Hence, the pair (p,Ltot)(\sqrt{p},L_{\mathrm{tot}}) completely determines the virtual state of the active portion of the pool. Once these two quantities are known, the swap equations and the local trading geometry are fully determined.

Net liquidity

For each initialized tick index ii, the net-liquidity variable ΔL(i)\Delta L(i) measures the jump in active liquidity when the price crosses that tick from left to right, that is, from lower prices to higher prices. Accordingly:

LtotLtot+ΔL(i),L_{\mathrm{tot}} \leftarrow L_{\mathrm{tot}} + \Delta L(i), LtotLtotΔL(i).L_{\mathrm{tot}} \leftarrow L_{\mathrm{tot}} - \Delta L(i).

To illustrate this rule, consider three positions:

P1:[t1,t3],L1=4,P_1:[t_1,t_3], \quad L_1=4, P2:[t2,t4],L2=7,P_2:[t_2,t_4], \quad L_2=7, P3:[t4,t5],L3=3,P_3:[t_4,t_5], \quad L_3=3,

where t1<t2<t3<t4<t5t_1<t_2<t_3<t_4<t_5 are initialized ticks. Moving from low prices to high prices, we obtain

ΔL(t1)=4,ΔL(t2)=7,ΔL(t3)=4,\Delta L(t_1)=4, \qquad \Delta L(t_2)=7, \qquad \Delta L(t_3)=-4, ΔL(t4)=37=4,ΔL(t5)=3.\Delta L(t_4)=3-7=-4, \qquad \Delta L(t_5)=-3.

Thus, the active liquidity increases when a position begins, decreases when a position ends, and changes by the algebraic sum of those effects when multiple positions begin or end at the same tick.

Gross liquidity

For each initialized tick ii, the gross-liquidity variable Lg(i)L_g(i) is defined as the sum of the liquidity parameters of all positions that reference tick ii as one of their two boundaries.

This variable serves a different purpose from net liquidity. Net liquidity tells the contract how the total active liquidity changes when the price crosses a tick. Gross liquidity, by contrast, tells the contract whether the tick is still needed at all. If the last position that references a tick is removed, then Lg(i)L_g(i) becomes zero, and the tick may be uninitialized.

It is crucial to note that gross liquidity cannot be reconstructed from net liquidity alone. A tick may have zero net liquidity and nevertheless still be referenced by one or more positions.

Example (Net and gross liquidity)

Consider a CenturionDEX v3 pool with four positions:

P1:[t1,t2],L1=3,P_1:[t_1,t_2],\quad L_1=3, P2:[t2,t4],L2=4,P_2:[t_2,t_4],\quad L_2=4, P3:[t3,t5],L3=2,P_3:[t_3,t_5],\quad L_3=2, P4:[t5,t6],L4=2,P_4:[t_5,t_6],\quad L_4=2,

where

t1<t2<t3<t4<t5<t6.t_1<t_2<t_3<t_4<t_5<t_6.

These are the only initialized ticks in the pool.

Moving from lower prices to higher prices, the net-liquidity updates are obtained as follows:

ΔL(t2)=43=1;\Delta L(t_2)=4-3=1; ΔL(t3)=2;\Delta L(t_3)=2; ΔL(t4)=4;\Delta L(t_4)=-4; ΔL(t5)=22=0;\Delta L(t_5)=2-2=0; ΔL(t6)=2.\Delta L(t_6)=-2.

The corresponding gross-liquidity values are determined by summing the liquidity parameters of all positions that reference each tick:

These values are summarized below:

Initialized tickt1t_1t2t_2t3t_3t4t_4t5t_5t6t_6
Net liquidity ΔL\Delta L3311224-4002-2
Gross liquidity LgL_g337722444422

In particular, notice that

ΔL(t5)=0butLg(t5)=4.\Delta L(t_5)=0 \quad\text{but}\quad L_g(t_5)=4.

This shows that zero net liquidity at a tick does not imply that the tick can be discarded. The tick t5t_5 is still referenced by two active position boundaries and must therefore remain initialized.

Global fee growth

Because trading fees are stored outside the active liquidity rather than reinvested into it, the protocol must keep separate track of the total fees collected in each token. It does so through the global fee-growth variables

fgX,fgY.f_g^X,\qquad f_g^Y.

These quantities measure cumulative fees per unit of liquidity in tokens XX and YY, respectively. Storing fee growth on a per-unit-liquidity basis is essential: if the contract stored only absolute fee totals, then any change in active liquidity would make it difficult to determine how much of the accumulated fees belongs to a given position.

Fee growth outside a tick

Let ii be an initialized tick index, and let

t(i)=1.0001it(i)=1.0001^i

be the corresponding tick price. The tick t(i)t(i) partitions the positive price axis into the two intervals

[0,t(i))and[t(i),+).[0,t(i)) \qquad\text{and}\qquad [t(i),+\infty).

Given the current price pp, we define the outer interval associated with tick ii to be the one of these two intervals that does not contain pp. Equivalently:

[0,t(i));[0,t(i)); [t(i),+).[t(i),+\infty).

For each initialized tick ii, the protocol stores

foX(i),foY(i),f_o^X(i),\qquad f_o^Y(i),

which represent the cumulative fee growth per unit liquidity in the outer interval associated with tick ii, in tokens XX and YY, respectively.

For example, consider the tick from the earlier section whose index is

i=77,410,i=77{,}410,

so that

t(i)2299.882.t(i)\approx 2299.882.

If the current price is p=2450p=2450, then p>t(i)p>t(i), and the outer interval is

[0,2299.882).[0,2299.882).

If instead the current price is p=2100p=2100, then p<t(i)p<t(i), and the outer interval is

[2299.882,+).[2299.882,+\infty).

Whenever the price crosses an initialized tick ii, the outer interval switches from one side of the tick to the other. Consequently, the contract updates the stored outside-fee variables by the rule

foα(i)fgαfoα(i),α{X,Y}.f_o^\alpha(i)\leftarrow f_g^\alpha-f_o^\alpha(i), \qquad \alpha\in\{X,Y\}.

This works because the total fee growth is the sum of the fee growth on the two sides of the tick.

When a tick is initialized for the first time, its outside-fee variables are assigned the values

foα(i)={fgα,iic,0,i>ic,α{X,Y}.f_o^\alpha(i)= \begin{cases} f_g^\alpha, & i\le i_c,\\[4pt] 0, & i>i_c, \end{cases} \qquad \alpha\in\{X,Y\}.

This initialization convention should not be interpreted as a literal decomposition of previously accumulated fees. Rather, foX(i)f_o^X(i) and foY(i)f_o^Y(i) are auxiliary bookkeeping variables whose purpose is to ensure that the future fee share of each position can be computed correctly.