---
# System prepended metadata

title: 'Uniswap V3 for ~~dummies~~ boludos: Solving the fee tracking problem'
tags: [UniswapV3]

---

# What happens when a user provides liquidity?
When a user mints liquidity in Uniswap V3, an entry is added to a mapping indicating the provided liquidity, and this entry is uniquely identified by tuples `(minTick, maxTick, positionOwner`. This position is non-transferable. Thus, it can be likened to a non-transferable NFT, though it does not adhere to EIP-721 or EIP-1155 standards. This type of position is created whenever a user decides to provide liquidity. The primary objective for liquidity providers is to earn fees from swaps conducted using their provided liquidity. But how exactly are fees tracked in Uniswap V3 to accomplish this goal?

First of all, to streamline the protocol design:
1. Unlike Uniswap V2, fees do not automatically compound.
2. Fees paid in each token are tracked separately.
3. Two variables are created to track how much fees have been paid above or below each tick. The relevance of this will hopefully become clearer by the end of this article.

The primary calculation is structured as follows:
$$f(i_{L},i_{U}) = f_{g} - f_{b}(i_{L}) - f_{a}(i_{U})$$
* $f(i_{L},i_{U})$ Fee growth inside price range corresponding to ticks in $[i_{L}, i_{U}]$
* $f_{b}(i_{L})$: Fees paid below tick $i_{L}$
* $f_{a}(i_{U})$: Fees paid above tick $i_{U}$
* $f_{g}$: Fees paid so far.


Understanding that the fees paid below a given tick and the fees paid above this tick complement each other to total the current global fees, we can deduce that $f_{b}(i) = f_{g} - f_{a}(i)$ for every tick. Uniswap V3 tracks $f_{g}$ consistently, which means it is only necessary to track either $f_{b}(i)$ or $f_{a}(i)$ for each tick. How do we decide which to track?

## Should we track $f_{b}(i)$ or $f_{a}(i)$?
This is not the right question; instead, we should ask **when** should we track $f_{b}(i)$ and **when** should we track $f_{a}(i)$? When liquidity is provided in the price range $[P_{A}; P_{B})$, it indicates an interest to track $f_{b}(i_{P_{A}})$ / $f_{a}(i_{P_{A}})$ and $f_{b}(i_{P_{B}})$ / $f_{a}(i_{P_{B}})$.

If no one has provided liquidity in a price range that includes $P_{D}$ as one of its extremes, there's no need to track $f_{b}(i_{P_{D}})$ / $f_{a}(i_{P_{D}})$. But what should be tracked? If someone is already providing liquidity in a price range that contains $P_{A}$/$P_{B}$ as one of its boundaries, no new tracking actions are required for $P_{A}$/$P_{B}$. However, if the current price is above $P_{A}$/$P_{B}$, we must record global fees, thereby documenting the fees paid below $P_{A}$/$P_{B}$. Assuming the price never dips below $P_{A}$/$P_{B}$, we can use the current global fees and subtract the value recorded to calculate the fees paid above $P_{A}$/$P_{B}$.

Conversely, if the current price is below $P_{A}$/$P_{B}$, we take no action. Why? If $P_{A}$/$P_{B}$ is never reached, the fees paid below $P_{A}$/$P_{B}$ will always correspond to the global fees, while the fees paid above $P_{A}$/$P_{B}$ will remain at $0$.

Confused? Perhaps the next diagram will help clarify:

![Tracking fees](https://i.ibb.co/whtQwGb/Uniswap-dynamic-fees-P-gina-4.png)


What happen when we cross a tick? If the tick is tracking $f_{b}(i)$ we start tracking $f_{a}(i)$, and we do the opposite otherwise. If we are tracking nothing:
* If no one is providing liquidity in a price range that contains price corresponding to $i$ as one of its limits, we do nothing
* Otherwise:
    * If we are not tracking nothing, this is the first time we are crossing tick $i$ since some started providing liquidity. This person must have provided liquidity when $P_{c} > P_{i}$, therefore now $f_{a}(i) = f_{g}(i)$ and $f_{b}(i) = 0$. So we are going to start tracking $f_{a}(i)$.
    * If we are tracking $f_{b}(i)$ we start tracking $f_{a} = f_{g}(i) - f_{b}(i)$ considering $f_{g}(i)$ as the global fees when tick $i$ is crossed.
    * If we are tracking $f_{a}(i)$ we start tracking $f_{b} = f_{g}(i) - f_{a}(i)$ considering $f_{g}(i)$ as the global fees when tick $i$ is crossed.
The fees that tick $i$ is tracking is going to be called tick outside tick $i$, and it will be represented by $f_{o}(i)$.
   
Ok, this seems messy, let's see it in other way:

![Fees outside algorithm](https://i.ibb.co/W6TV1tt/Fees001-2.png)

![Fees outside algorithm](https://i.ibb.co/QX9mCqJ/Fees002-2.png)

Still messy? Yeah I know, let's see an example of how this behaves.

1. User A comes and provide $1,000,000$ amount of liquidity in price range $[0.95;1.05)$ at a current price of $1$, being the first to add liquidity in the pool. In this case:
    * $f_{o}(i_{0.95}) = f_{g} = 0$
    * $f_{o}(i_{1.05})$ is not initialized
![Fee growth 01](https://i.ibb.co/ZSQX595/Fee-Increase001.png)
2. Swaps are done just in price range $[0.95;1.05)$ and $f_{g} = 1,000$ now. Current price still in $1$. All this fees corresponds to user A
![Fee growth 02](https://i.ibb.co/5k2F11p/Fee-Increase002.png)
3. User B comes and provide liquidty in price range $[0.99;1.03)$ for $1,000,000$ amount of liquidity, then:
    * $f_{o}(i_{0.99}) = f_{g} = 1,000$
    * $f_{o}(i_{1.03})$ is not initialized
![Fee growth 03](https://i.ibb.co/f20r1tj/Fee-Increase003.png)
4. After multiples swaps price moves from $1$ to $1.03$ generating $1,000$ in fees ($500$ corresponds to user $A$ and $500$ to user $B$), and the goes to $1.04$ generating $100$ fees (these fees corresponds to user A). Therefore, when $1.03$ is crossed $f_{g} = 2,000$ in fees has been historically collected, so $f_{o}(i_{1.03}) = f_{b}(i) = f_{g} = 2,000$. When price $1.04$ is reached $f_{g} = 2,100$. 
![Fee growth 04](https://i.ibb.co/y80ndxb/Fee-Increase004.png)
![Fee growth 05](https://i.ibb.co/xJrHnr6/Fee-Increase005.png)
5. After more swaps, $300$ in fees are collected (which correspond to user A), and price moves to $1.03$. When the price reaches $1.03$, $f_{g} = 2,400$, so $f'_{o}(i_{1.03}) = f_{g} - f_{o}(i_{1.03}) = f_{g} - f_{b}(i_{1.03}) = 2,400 - 2,100 = 300 = f_{a}(i)$. Then $100$ in fees are collected and the price goes to $1$ (from these fees $50$ correspond to user A and $50$ to user B), so $f_{g} = 2,500$.
![Fee growth 06](https://i.ibb.co/RY0fkFq/Fee-Increase006.png)
![Fee growth 07](https://i.ibb.co/zXXH0CZ/Fee-Increase007.png)
6. Finaly a swap, $100$ in fees are collected, and price moves to $1$ to $1.03$, $100$ fees are collected (50$ correspond to user A and $50$ to user B). When the price reaches $1.03$, $f_{g} = 2,600$, so $f'_{o}(i_{1.03}) = f_{g} - f_{o}(i_{1.03}) = f_{g} - f_{a}(i_{1.03}) = 2,600 - 300 = 2,300 = f_{b}(i)$. Then $100$ in fees are collected (all correspond to user A) and the price goes to $1.04$, so $f_{g} = 2,900$
![Fee growth 08](https://i.ibb.co/9r7BnHG/Fee-Increase008.png)
![Fee growth 09](https://i.ibb.co/pbWkzLd/Fee-Increase009.png)


Now, how do we calculate the earned fees between price ranges:
* $[0.95;1.05)$? In this case it is $f_{g}$
* $[0.95;1.03)$? $f_o(i_{1.03}) = 2,300$ and $f_o(i_{0.95}) = 0$, given that current price is above $1.03$ we know can assure that $f_o(i_{1.03}) = f_{b}(1.03)$, so $f_{a}(1.03) = f_{g} - f_o(i_{1.03}) = 2,700 - 2,300 = 400$. For this same reason, we know that $f_o(i_{0.95}) = f_{b}(0.95) = 0$. So, the fees earned in price range $[0.95;1.03)$ ($f_{0.95;1.03}$ from now on) correspond to $f_{g} - f_{a}(1.03) - f_{b}(0.95)$. In this case: 
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = f_{g} - f_{o}(1.03) - f_{o}(0.95)$$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = 2,900 - 500 - 0 $$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = 2,400$$
* $[0.95;0.99)$? $f_o(i_{0.99}) = 0$ and $f_o(i_{0.95}) = 0$. Not only intutively this is $0$ because no swap has occur between this price range, from previous example if we want to calculate fees accrued in price range.
* $[0.99;1.03)$? After analogous calculus we can conclude that $f_{0.99;1.03} = f_{o}(1.03) - f_{o}(0.99) = 2,300 $
* $[0.99;1.05)$? Intuitively we have never crossed these ticks, so the fees paid in this price range correspond to global fees
* $[1.03;1.05)$: $1.03 < P_{c} < 1.05$. $f_{o}(1.03) = f_{b}(1.03)$, therefore:

$$f_{1.03;1.05} = f_{g} - f_{b}(1.03) - f_{a}(1.05)$$
$$f_{1.03;1.05} = f_{g}  - f_{o}(1.03) - f_{o}(1.05)$$ 
$$f_{1.03;1.05} = 2,700  - 2,600 - 0$$ 
$$f_{1.03;1.05} = 100$$ 


To play a little more with some numerical values, let's assume that there were many swap and the price moved from $1.04$ to $1.03$ accruing $100$ in fees (all correspond to user A) and ended up in $1.02$ accruing another $100$ in fees (50$ correspond to user A and $50$ to user B).

![Fee growth 10](https://i.ibb.co/hH82hZy/Fee-Increase010.png)
![Fee growth 11](https://i.ibb.co/8N9zRRM/Fee-Increase011.png)

So, how do we now calculate the earned fees between price ranges:
* $[0.95;1.05)$? Still $f_{g}$
* $[0.95;1.03)$? $0.95 < P_{c} < 1.03$; $f_o(i_{1.03}) = 500$; and $f_o(i_{0.95}) = 0$. Given that $P_{c} > 1.03$ we know can assure that $f_o(i_{1.03}) = f_{a}(1.03) = 500$. Given that $0.95 < P_{c}$ then $f_o(i_{0.95}) = f_{b}(0.95) = 0$. So, the fees earned in price range $[0.95;1.05)$ ($f_{0.95;1.03}$ from now on) correspond to $f_{g} - f_{a}(1.03) - f_{b}(0.95)$. In this case: 
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = f_{g} - (f_{g} - f_{o}(1.03)) - f_{o}(0.95) $$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = (f_{g} - f_{g}) + f_{o}(1.03) - f_{o}(0.95) $$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = f_{o}(1.03) - f_{o}(0.95) $$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = 2,300 - 0$$
    $$f_{g} - f_{a}(1.03) - f_{b}(0.95) = 2,300$$
* $[0.95;0.99)$? We have never crossed this prices so we have accrues $0$ fees
* $(0.99;1.03)$: This case is similar to price range $[0.95;1.03)$, so in this case: $f_{0.99;1.03} = f_{g} - f_{b}(0.99) - f_{b}(1.03) = f_{g} - f_{o}(0.99) - f_{o}(1.03)$
* $[0.99;1.05)$? Intuitively we have never crossed these ticks, so the fees paid in this price range correspond to global fees
* $[1.03;1.05)$?: $P_{c} < 1.03 < 1.05$, then $f_{o}(1.03) = f_{a}(1.03)$; $f_{o}(1.05) = f_{a}(1.05)$; and $f_{1.03;1.05} = f_{a}(1.03) - f_{a}(1.05) = f_{o}(1.03) - f_{o}(1.05)$ 

In this way the total fees accrued depends not only on the recorded values for each tick, but also on the curren price. Then $f_{i_{A};i_{b}} =$
* If $i_{c} \leq i_{A}$: $f_{o}(i_{A}) - f_{o}(i_{B})$
* If $i_{c} \geq i_{A}$: $f_{o}(i_{B}) - f_{o}(i_{A})$
* If $i_{A} < i_{c} < i_{B}$: $f_{g} - f_{o}(i_{A}) - f_{o}(i_{B})$

There is still one key question pending to ask: How much fees corresponds to each position?

## The liquidity providers fee cut
As noted in our previous graph where we manually tracked the fees corresponding to users A and B, such an approach is not sustainable if we are dealing with thousands of positions. If we were to adopt this method for each swap, it would necessitate:
1. Iterating through each position.
2. If the position encompasses the tick where the swap is conducted, we would need to allocate rewards based on the formula: $\frac{ \text{position liquidity}}{\text{current liquidity}} \times \text{paid fees}$.
3. Modifying a state variable that tracks the fees accumulated by each position.

Anyone familiar with the Ethereum Virtual Machine (EVM) or blockchain technology in general would quickly realize that this approach is not viable due to its high computational and transactional costs.

From the previous algorithm, we observe that a position is rewarded if and only if its liquidity is active within the current liquidity pool. Therefore, if we could track the historical fees paid per unit of liquidity, we might be able to use this information to calculate fees for each position. Let’s introduce a variable called fee growth ($f_{growth}$). This variable is only updated when a swap occurs as follows:

$$f'_{growth} = f_{growth} + \frac{fees \; paid_{swap}}{current \; liquidity}$$

A good interpretation of this value is: "If someone has provided $1$ liquidity unit within the price range $[1.0001^{- \text{MIN TICK}};1.0001^{- \text{MAX TICK}})$ since the pool was initialized, and they have never claimed their fees, they should be entitled to claim $f_{growth}$ in fees."

Now, to utilize this interpretation for calculating the fees corresponding to each position, we proceed as follows:

Initially, each tick, rather than solely tracking $f_{g}$ (the global fees), will track $f_{growth}$. This modification means that the previously used algorithms will be updated to:

![Fee growth 01](https://i.ibb.co/p0Hnkdm/Fee-growth-flow-01.png)

![Fee growth 02](https://i.ibb.co/7WHQq0j/Fee-growth-flow-02.png)

![Fee growth 03](https://i.ibb.co/dGmTbkP/Fee-growth-03.png)

Here:
* $f_{growth}$: Historical fees paid per liquidity unit
* $f_{go}(i)$: Fee growth outside tick $i$. Sometimes it will correspond to $f_{gb}(i)$, and others to $f_{gb}(i)$
* $f_{gb}(i)$: Fee growth below tick $i$
* $f_{ga}(i)$: Fee growth above tick $i$


The interpretation of the last algorithm is crucial for understanding the mechanism by which fees can be claimed in Uniswap V3. It can be articulated as: "If someone has provided $1$ liquidity unit within the price range $[P_{A}; P_{B})$ since the pool was initialized, and they have never claimed their fees, they should be able to claim fees using the exposed algorithm (the last one)."


Now that we understand this principle, a straightforward method to calculate the fees corresponding to a position within the price range $[P_{A}; P_{B})$ involves tracking how much historical fee growth pertains to this price range thus far. To determine the amount of fees due to a position, you need to:
1. Calculate the current historical fee growth for the price range $[P_{A}; P_{B})$.
2. Subtract the historical fee growth that was recorded when the position was created.
3. Multiply this difference by the amount of liquidity provided in the position.


$$Fees(position(P_{A};P_{B}; owner)): historicalFees(P_{A};P_{B}) - position(P_{A};P_{B}; owner).recodedHistoricalFeesForPriceRange$$

In this way we have an easy and escalable way to calculate how much fees correspond to each position.

# One last thing
If you have reached here and understood each of the articles from this series: HUGE CONGRATS! Now you have strong Uniswap V3 basis and you are more than able to start diving into it code base and understand it.

As next step you can go by your own through [Uniswap V3 codebase](https://github.com/Uniswap/v3-core) and try to understand it.

As addition resources to these articles I recommend you:
* [Uniswap V3 Development Book](https://uniswapv3book.com) by [Jeiwan](https://twitter.com/jeiwan7)
* [Smart contract programmer Uniswap V3 playlist](https://www.youtube.com/watch?v=WW_xRGXSr7Q&list=PLO5VPQH6OWdXp2_Nk8U7V-zh7suI05i0E)
