How Uniswap V2 computes the mintFee

Uniswap V2 was designed to collect 1/6th of the swap fees to the protocol. Since a swap fee is 0.3%, 1/6th of that is 0.05%, so 0.05% of every trade would go to the protocol.

Although this feature was never actually activated, we discuss this feature anyway since some forks may use it. It is also very easy to get the calculation wrong, so investing the time now to understand it will help you catch errors in similar calculations later.

Prerequisites

You need to be familiar with all the prior chapters in the Uniswap V2 Book to be able to follow along.

Collecting protocol fees during swaps is inefficient

It would be inefficient to collect 0.05% of the fee on every trade because that would require additional token transfers. Transferring ERC20 tokens requires a storage update, so transferring to two addresses instead of one would be considerably more expensive.

Therefore, the fee is collected when a liquidity provider calls burn or mint. Since these operations are infrequent compared to [swapping tokens](swapping tokens), this will lead to gas savings. To collect the mintFee, the contract calculates the amount of fees collected since that last happened, and mints enough LP tokens to the beneficiary address such that the beneficiary is entitled to 1/6th of the fee.

Terminology of fee and mintFee

To avoid confusion in terminology, we refer to “fee” as the 0.3% collected from traders during the swap, and the “mintFee” the 1/6th of the 0.3% fee. Yes, having fee in both terms is not great nomenclature, but that’s what we have to work with.

Liquidity is the square root of the products of the token balances in the pool. The justification for this formula was discussed in the Uniswap V2 swap function article. Some literature refers to this as $\sqrt{k}$ where $k = xy$ and $x$ and $x$ are the token balances in the pool (the reserves of $x$ and $y$). We refer to liquidity with $\ell$ since it is shorter to write than $\sqrt{k}$.

Computing the mintFee assumptions

For this to work, Uniswap V2 relies on the following two invariants:

  • If mint() and burn() are not called, the liquidity of the pool can only increase.
  • The increase in liquidity is purely due to fees (or donations). By measuring the increase in liquidity since the last mint() or burn() transaction, the pool knows how much fees were collected.

These would be good invariant tests to write, but for now we will take for granted that they are true.

Example calculation of mintFee

Suppose at $t_1$ the pool starts at 10 token0 and 10 token1.

After a lot of trading and fee collection, the new pool balance is 40 token0 and 40 token1 at $t_2$.

Liquidity is measured as the square root of the product of the two tokens, i.e. $\ell = \sqrt{k}$. The liquidity was 10 at $t_1$ and 40 at $t_2$. Said another way, $\ell_1 = 10$ and $\ell_2 = 40$. We are going to charge a fee on the growth from 10 to 40.

30 units of liquidity out of the total 40 at $t_2$ is due to swap fees.

We want to mint enough LP tokens, the “mintFee” such that the beneficiary receives 1/6th of the “fee portion” of the pool. That is, they should be entitled to 5 units of liquidity (30 / 6) that came from profit.

The protocol should not collect fees on any of the “original liquidiy” i.e. $\ell_1$. The protocol should only collect fees on the delta, i.e. $\ell_2 – \ell_1$.

When mint() or burn() is called, Uniswap mints LP tokens to the protocol fee recipient. This causes a dilution such that the current supply of LP tokens can redeem the original liquidity plus 5/6ths of the “profit liquidity” (liquidity from swap fees).

Deriving the mint fee formula

Let’s use the following notation:

  • $s$ supply of LP tokens before the dilutive protocol fee LP tokens are minted.

  • $\eta$ is the amount of LP tokens that will be minted to the protocol. It should be enough to redeem 1/6th of the profit liquidity.

  • $\ell_1$ is the liquidity of the original deposit, i.e. liquidity the LPs provided.

  • $\ell_2$ is the liquidity of the original deposits and the liquidity that results from swap fees.

  • $d$ is the amount of liquidity owed to the LPs net of the protocol fee. That is, the LPs should be entitled to their original deposit and 5/6ths of the profit.

  • $p$ is the amount of liquidity owed to the protocol. This is 5/6ths of $\ell_2 – \ell_1$.

To compute $\eta$ we observe the following invariant must be true:

$$ \frac{\eta}{p}=\frac{s}{d} $$

In other words, the previous total supply, $s$ LP tokens can redeem the liquidity due to the LPs, and $\eta$ LP tokens can redeem the amount due to the protocol.

The graphic below solves for $\eta$ in terms of the change in liquidity.

algebraic derivation of the protocol fee

The _mintFee() code of Uniswap V2

With that derivation in mind, the bulk of the Uniswap V2 _mintFee function should be self-explanatory. Here are some changes in notation:

  • current liquidity after fees $\ell_2$ is rootK
  • previous liquidity $\ell_1$ is kLast
  • the supply of LP tokens before dilution $s$ is totalSupply
  • the function is state changing, it mints the mintFee inside the function rather than return the calculation of the mintFee (blue highlight)
  • the fee can be switched on an off with the flag feeOn which we haven’t discussed yet

_mintFee() solidity code

We will dive into this function some more, but first we want to note where kLast gets updated.

Where klast gets updated

In the code above, kLast is not set unless feeOn is switched to false. It is set at the completion of mint and burn but not swap because we are interested in measuring the growth of fees due to swaps between liquidity deposit and withdrawal events. The place kLast is set is marked with a yellow box.

Mint function updating klast

Mint function from Uniswap with the fee switch highlighted

Burn function updating klast

Burn function from Uniswap with the fee switch highlighted

_mintFee code conditions

Now that we understand how kLast is updated, we can fully explain the _mintFee function. _mintFee function highlighted at branching points

Let’s consider the possibilities in the code snippet above, repeated for convenience.

  1. The feeOn is false, nothing is minted (green highlight)
  2. The feeOn is false, kLast is zero (yellow highlight)
  3. The feeOn is false, kLast is not zero (yellow highlight)
  4. The feeOn is true, but there was no growth in liquidity (orange highlight)
  5. The feeOn is true, and there was liquidity growth (orange highlight), so the mint fee applies (blue highlight)

It’s easier to see the logic in a decision tree, so here is the decision tree with the branches colored the same as the if statements.

decision tree of _mintFee showing the 5 branches

Learn more with RareSkills

Please see our blockchain bootcamp to see our course offerings.

Originally Published November 14, 2023

get_D() and get_y() in Curve StableSwap

get_D() and get_y() in Curve StableSwap This article shows algebraically step-by-step how the code for get_D() and get_y() are derived from the StableSwap invariant. Given the StableSwap Invariant: $$ An^n\sum x_i +D=An^nD+\frac{D^{n+1}}{n^n\prod x_i} $$ There are two frequent math operations we wish to conduct with it: Compute $D$ given fixed values for $A$, and the […]

Fixed Point Arithmetic in Solidity (Using Solady, Solmate, and ABDK as Examples)

Fixed Point Arithmetic in Solidity (Using Solady, Solmate, and ABDK as Examples) A fixed-point number is an integer that stores only the numerator of a fraction — while the denominator is implied. This type of arithmetic is not necessary in most programming languages because they have floating point numbers. It is necessary in Solidity because […]

Uniswap V2: Calculating the Settlement Price of an AMM Swap

Uniswap V2: Calculating the Settlement Price of an AMM Swap This article explains how to determine the price settlement of a trading pair in an Automated Market Maker (AMM). It answers the question of “How many token X can be swapped for token Y from the AMM?”. The swap() function on Uniswap V2 requires you […]

How Chainlink Price Feeds Work

How Chainlink Price Feeds Work Chainlink price oracles are smart contracts with public view functions that return the price of a particular asset denominated in USD. Off-chain nodes collect the prices from various sources like exchanges and write the price data to the smart contract. Here is the smart contract for getting the price of […]

Featured Jobs

RareSkills Researcher

As a RareSkills researcher, you will be contributing to the technical content we post on our website.

Apply Now
Rust/Solana Auditor

We’re looking for someone to design and implement security measures and defense-in-depth controls to prevent and limit vulnerabilities.

Apply Now
Full Stack Developer

We’re looking for a Senior Full-Stack Engineer to play a foundational role in working across the entire offchain stack of products.

Apply Now
Rust Developer

We are seeking a talented Rust Developer to build a robust, scalable blockchain indexers and analytic backend.

Apply Now