# Recap * Uniswap V2 invariant: $x \times y = k$ * Uniswap V3 invariant: $(x_{virtual \; liquidity} + x_{real \; liquidity})\times (y_{virtual \; liquidity} + y_{real \; liquidity}) = L^{2}$ * $x_{virtual\; liquidity} = \frac{L}{\sqrt{P_{B}}}$ * $y_{virtual\; liquidity} = L \times \sqrt{P_{A}}$ * Next equations can be used to calculate the liquidity variations when we add or remove liquidity: * When $P_{A} \leq P_{c} < P_{B}$: * $\Delta L_{P_{A}, P_{c}} = \frac{\Delta x}{\frac{1}{\sqrt{P_{A}}} - \frac{1}{\sqrt{P_{c}}}}$ * $\Delta L_{P_{c},P_{B}} = \frac{\Delta y}{\sqrt{P_{B}} - \sqrt{P_{c}}}$ * $\Delta L_{P_{A},P_{B}} = min(\Delta L_{P_{A},P_{c}}, \Delta L_{P_{c}, P_{B}})$ * $\Delta x = \Delta L_{P_{A},P_{B}} \times (\frac{1}{\sqrt{P_{c}}} - \frac{1}{\sqrt{P_{B}}})$ * $\Delta y = \Delta L_{P_{A},P_{B}} \times (\sqrt{P_{c}} - \sqrt{P_{A}})$ * When $P_{c} > P_{B}$: $\Delta L_{P_{A},P_{B}} = \frac{\Delta x}{\frac{1}{\sqrt{P_{A}}} - \frac{1}{\sqrt{P_{B}}}}$ * When $P_{c} \leq P_{A}$: $\Delta L_{P_{A}, P_{B}} = \frac{\Delta y}{\sqrt{P_{B}} - \sqrt{P_{A}}}$ * $P_{c} = \frac{y_{virtual\; liquidity} + y_{real\; liquidity}}{x_{virtual\; liquidity} + x_{real\; liquidity}}$ * When adding a specific amount of liquidity $l$ within the price range $[P_A,P_B)$: * If $P_c \geq P_B$: all the liquidity must be in token Y. * If $P_c < P_A$: all the liquidity must be in token X. * If $P_A \leq P_c < P_B$: it is advisable to split the calculation to determine how much of each token should be added. * A key concept of Uniswap V3 involves tracking net liquidity for each price. When liquidity is added within the range $[P_A;P_B)$, the net liquidity associated with $P_A$ increases, while it decreases at $P_B$. Moreover, if $P_c$ falls within $[P_A;P_B)$, the currently active liquidity increases. * If $P_c < P_k$, and after a swap, $P_k \leq P_c'$, the current liquidity is increased by the net liquidity associated with $P_k$. Conversely, if $P_c \geq P_k$ and after a swap $P_k < P_c'$, the current liquidity is reduced by the net liquidity associated with $P_k$. * Price changes can only occur through swaps. If updates to current liquidity are triggered each time a price point is crossed, the swap function could become significantly more costly to execute. # Math granularity in computers Can we define a mathematical function $f: \mathbb{R}^{+} \rightarrow \mathbb{R}^{+}$ that always returns a number lower than its input? The answer is yes, an example being $f(x) = 10^{-x}$. To simplify for those less familiar with mathematics: if I provide a number, is it possible to always name a number lower than the one given? Absolutely, yes. However, can we create a program that invariably outputs a number lower than the one inputted? Unfortunately, the answer is no. The reason for this lies in the limitation of how numbers are represented digitally. Specifically, there is a maximum and minimum boundary for the number of bits used to represent a number. For example, in the programming language Solidity, the minimum value for a 256-bit integer is defined as `type(int256).min = -2^{256 - 1}`. Consequently, the range of numbers that can be represented in Solidity (or any programming language) is bound by these limitations. # Tick concept Uniswap opted to restrict the price ranges within which users can supply liquidity to simplify the implementation of V3, thus reducing the granularity of the price range. This limitation is defined by a concept known as a tick. A tick is defined by the formula $p(i) = 1.0001^{i}$, equivalently $(100% + 0.01%)^{i}$, where $i \in \mathbb{Z}$. Here, $i$ can be either positive or negative, allowing for the representation of numbers smaller than $1$, with $i$ serving as the tick index. For the V3 implementation, $i$ is constrained within $\mathbb{Z}_{128}$, implying that $i$ ranges from $[-2^{128}, 2^{128}]$. If we denote a tick by $p(i)$, then $p(i)$ falls within $[\log{1.0001}(2^{-128}), \log_{1.0001}(2^{128})]$. This results in $p(i)$ spanning $[\log_{1.0001}(2^{-128}), \log_{1.0001}(2^{128})]$. It's crucial to highlight that, despite the requirement to provide liquidity within a price range limited to tick values, the actual price within this range can fluctuate freely between any values. ### Calculate tick index based on a price 1. $P = 1.0001^{i}$ 2. $\sqrt{P} = \sqrt{1.0001^{i}}$ (This is more convenient to us given that $\sqrt{P}$ is stored on chain and not $P$) 3. $i = log_{\sqrt{1.0001}} \sqrt{P}$ 4. $i_{implementation} = \lfloor log_{\sqrt{1.0001}} \sqrt{P} \rfloor$ (For implementation we round down) ### Tick spacing Limiting the price ranges to specific ticks where users can provide liquidity significantly mitigates the risk of a DoS attack that could occur when a very small amount of liquidity is provided across numerous price ranges. However, this measure alone is insufficient. To further refine this approach, Uniswap mandates that users provide liquidity within price ranges bounded by ticks that are multiples of a predetermined number, introducing the concept of tick spacing. [As of the current Uniswap V3 factory contract, this tick spacing is enforced at values of 1, 10, 600, and 200](https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Factory.sol#L22-L32). But how does this work in practice? Consider the example from [the previous article](https://hackmd.io/@carlitox477/SyxVHD4kC) where a user wishes to provide up to $1,000$ tokens $X$ and $1,000$ tokens $Y$ within the price range $[0.95;1.05)$ and a current price $P_c = 1.01$. First, calculate the tick for the price range limits: * For $0.95$: $i_{0.95} = \lfloor \log_{\sqrt{1.0001}} \sqrt{0.95} \rfloor = -512$ * For $1.05$: $i_{1.05} = \lfloor \log_{\sqrt{1.0001}} \sqrt{1.05} \rfloor = 487$ The next step involves enforcing liquidity provision within price limits that are multiples of a selected tick spacing. The choice of tick spacing depends on the specific pool in which liquidity is to be provided. Unlike Uniswap V2, where pools are identified solely by their uple `(token0;token1)`, V3 pools are also defined by their tick spacing. This can result in multiple pools for the same token pair but with different tick spacings. The choice of tick spacing is ultimately up to the liquidity provider, but a more stable exchange rate usually favors a smaller tick spacing because it offers greater granularity. This allows for more liquidity to be provided within a narrower price range, optimizing the use of liquidity for pairs like $USDC$/$USDT$, where the exchange rate is expected to be close to $1$ under normal conditions. Assuming the involved tokens are stable, a tick spacing of 10 would be good enough. The user would then select a price range corresponding to a tick spacing of 10, for example, between $-520$ and $490$, thereby slightly adjusting the original price range to: * $1.0001^{-520} \approx 0.949331$ * $1.0001^{490} \approx 1.050217$ For the current price, the tick is always rounded down to determine the current price tick, resulting in a tick for the current price of $0$. The question then becomes: How many tokens from each should be added to provide liquidity within the price range $[1.0001^{-520}; 1.0001^{490}]$ at a current price of $1.01$? This requires splitting the problem into two parts, considering the limits and the current price (but not the tick corresponding to the current price): $[1.0001^{-520},1.01)$ and $[1.01,1.0001^{490})$_ * $\Delta L_{P_{c},P_{B}} = \frac{\Delta y}{\sqrt{P_{B}} - \sqrt{P_{c}}}$ 1. $\Delta L_{P_{B}, P_{c}} = \frac{1,000}{(\sqrt{1.0001^{490}} -\sqrt{1.01})}$ 2. $\Delta L_{P_{B}, P_{c}} \approx 51,979.62851$ * $\Delta L_{P_{A}, P_{c}} = \frac{ \Delta x}{\frac{1}{\sqrt{P_{A}}} - \frac{1}{\sqrt{P_{c}}}}$ 1. $\Delta L = \frac{ 1,000}{\frac{1}{\sqrt{1.0001^{-520}}} - \frac{1}{\sqrt{1.01}}}$ 2. $\Delta L \approx 32,625.143741$ * $\Delta L = min(\Delta L_{P_{A}, P_{c}},\Delta L_{P_{c}, P_{B}}) \approx 32,625.143741$ * $\Delta x = \Delta L_{P_{A},P_{B}} \times (\frac{1}{\sqrt{P_{c}}} - \frac{1}{\sqrt{P_{B}}}) \approx 627.65$ * $x_{virtual\; liquidity} = \frac{L}{\sqrt{P_{B}}} \approx \frac{ 32,625.143741}{\sqrt{1.0001^{490}}} \approx 31,835.578859$ * $y_{virtual\; liquidity} = L \times \sqrt{P_{A}} \approx 32,625.143741 \times \sqrt{1.0001^{-520}} \approx 31,787.863672$ ## Playground A playground to understand the mathematics involved when providing liquidity can be found in this [Google Sheet, specifically in the page titled "Playground 2: Ticks"](https://docs.google.com/spreadsheets/d/1CsfXp42ZqXEFLFUeFJaiOIN_hW_Tcy8VLuVwobrN4Cg/edit?usp=sharing). ## Liquidity tracking You might wonder why we require $P_A$ and $P_B$ to align with the $1.0001^i$ criteria, but not $P_c$. The rationale is to prevent DoS attacks on the swap algorithm, which will be discussed in the next lesson or article. To achieve this, liquidity tracking is managed as follows: * The current liquidity is always known. * Whenever the current price crosses a tick, liquidity will either be added or removed. For this mechanism to work, each tick must track the amount of liquidity to be added if crossed to the right, or removed if crossed to the left. Thus, when liquidity is added within the price range $[P_A, P_B)$, we increase the liquidity that should be added when the tick corresponding to these prices is crossed. This tracked liquidity is referred to as *net liquidity*. Therefore: * If $P_c < P_A$ and, after a swap, $P_c \geq P_A$, we add the net liquidity corresponding to the $P_A$ tick to the current liquidity. * If $P_c \geq P_B$ and, after a swap, $P_c < P_B$, we add the net liquidity corresponding to the $P_B$ tick to the current liquidity. * If $P_c \geq P_A$ and, after a swap, $P_c < P_A$, we remove the net liquidity corresponding to the $P_A$ tick from the current liquidity. * If $P_c < P_B$ and, after a swap, $P_c \geq P_B$, we remove the net liquidity corresponding to the $P_B$ tick from the current liquidity. While the exact mechanics of swapping in Uniswap V3 have yet to be discussed, this explanation provides a solid foundation on how it functions, which will be thoroughly covered in the next article.