© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
M. Ottina et al.Automated Market Makershttps://doi.org/10.1007/978-1-4842-8616-6_2

2. Uniswap v2

Miguel Ottina1  , Peter Johannes Steffensen2 and Jesper Kristensen3
(1)
Rivadavia, Argentina
(2)
Aarhus, Denmark
(3)
New York, NY, USA
 

Uniswap v2 is a decentralized exchange based on a system of smart contracts on the Ethereum blockchain and other networks (such as Polygon, Optimism, Arbitrum). It is formed by liquidity pools that enable automated market making; that is, they enable traders to buy and sell assets against the protocol without the need for a third party. Each Uniswap v2 liquidity pool consists of reserves of two ERC-20 tokens deposited by liquidity providers, who benefit from the fees that the protocol charges to traders. The collected fees are shared proportionally among all liquidity providers.

Uniswap v2 liquidity pools are based on a product formula that considers the amount of reserves of each of the two tokens of the pool. This product formula plays a crucial role in determining the amounts of each token involved in any trade. Consequently, the product formula determines the price of one of the pool tokens in terms of the other. Moreover, the price of an asset in a Uniswap v2 liquidity pool follows the actual market price of that asset due to the actions of external arbitrageurs, who detect price inconsistencies between the market price and the internal price of the liquidity pool and make a trade in order to gain an instant benefit from the difference between the said prices, making the internal price of the oracle once again equal to the market price.

In this chapter, we will explain in detail how the Uniswap v2 AMM works and delve into the mathematical concepts behind it. We will explain how the trading formulae and the spot price formula are derived from the product formula and show how liquidity providers can deposit and remove liquidity from the liquidity pools. In the process, we will analyze several interesting questions and give various illustrative examples.

2.1 Trading in a Uniswap v2 pool

Consider a Uniswap v2 liquidity pool formed by two tokens, X and Y. Let x and y be variables representing the amounts of tokens X and Y, respectively, that are in the pool at a particular moment. As we mentioned before, Uniswap v2 liquidity pools are based on a constant product formula. This means that the pool balances x and y satisfy the following equation:
$$ xcdot y={L}^2, $$
(2.1)

where L is a positive number that is called liquidity parameter of the pool. For the moment, we will assume that L is a constant value. We will see later how the liquidity parameter L can change.

In order to see how the constant product formula works, consider a Uniswap v2 liquidity pool with no fees whose reserves are 100 ETH and 400,000 USDC. From the constant product formula, we obtain that 100 ⋅ 400,000 = L2. Hence, L2 = 40,000,000. Suppose that a trader wants to buy 20 ETH. To do so, they must deposit a certain amount of USDC into the pool. Observe that if the pool sends 20 ETH to the trader, there will be 80 ETH left in the pool. From the constant product formula, we obtain that the balance B of USDC when there are 80 ETH left in the pool has to satisfy
$$ 80cdot B={L}^2=40,000,000. $$
Thus, B = 5,000,000. That is, if there are 80 ETH in the pool, then there must be 500,000 USDC in the pool. Therefore, the trader will have to deposit an amount of 500,000 – 400,000 = 100,000 USDC so as to be given 20 ETH. In other terms, the pool balances before and after the trade must satisfy Equation 2.1, and thus they can be regarded as points of the real plane that belong to the curve defined by Equation 2.1, as Figure 2-1 shows.
Figure 2-1

Pool states before and after the trade in a pool with no fees

We will now analyze how the constant product formula works in the general case in order to obtain several important formulae that will be used throughout this chapter. We will assume first that the liquidity pool has no fees.

Suppose that a trader wants to buy an amount a of token X. To do so, they must deposit an amount b of token Y. Let A and B be the balances in the pool of tokens X and Y, respectively, immediately before the trade. Then,
$$ AB={L}^2. $$
After the trade, the balance of token X in the pool will be Aa, and the balance of token Y in the pool will be B + b. Thus,
$$ left(A-a
ight)left(B+b
ight)={L}^2. $$
Hence,
$$ {L}^2=left(A-a
ight)left(B+b
ight)= AB+ Ab- aB- ab={L}^2+ Ab- aB- ab $$
and thus,
$$ Ab- aB- ab=0. $$
Therefore, we can isolate b to obtain
$$ b=frac{aB}{A-a}. $$
(2.2)

This means that in order to receive an amount a of token X, the trader must deposit an amount $$ frac{aB}{A-a} $$ of token Y.

Similarly, we can isolate a to obtain
$$ a=frac{Ab}{B+b}. $$
(2.3)

This means that if the trader deposits an amount b of token Y, they will receive an amount $$ frac{Ab}{B+b} $$ of token X.

2.1.1 Spot Price

A fundamental concept is that of the spot price. The spot price of token X in terms of token Y is defined as the price we pay per token X that we receive after depositing an infinitely small amount of token Y. In order to study the spot price and obtain a formula to compute it, we need to give a more formal definition.

Let A and B be the balances in the pool of tokens X and Y, respectively. Suppose that a trader deposits an amount b of token Y and receives an amount a of token X. Then, the price that the trader paid for each unit of token X is $$ frac{b}{a} $$. We also say that $$ frac{b}{a} $$ is the effective price (of token X in terms of token Y ) paid by the trader.

Let p be the spot price of token X in terms of token Y and let pe(b) be the effective price (of token X in terms of token Y) paid by the trader when depositing an amount b of token Y.

Then, the spot price is defined as
$$ p=underset{b	o 0}{lim }{p}_e(b)=underset{b	o 0}{lim}frac{b}{a} $$

(recall that a depends on b ).

Applying Equation 2.3, we obtain that
$$ {p}_e(b)=frac{b}{a}=frac{b}{frac{Ab}{B+b}}=frac{bleft(B+b
ight)}{Ab}=frac{B+b}{A}=frac{B}{A}+frac{b}{A}. $$
Thus,
$$ p=underset{b	o 0}{lim }{p}_e(b)=underset{b	o 0}{lim}left(frac{B}{A}+frac{b}{A}
ight)=frac{B}{A}. $$
(2.4)

Hence, the spot price when the pool state is (A, B) is equal to $$ frac{B}{A} $$, which coincides with the slope of the line that passes through the origin (0, 0) and the point (A, B).

Note also that since b > 0,
$$ {p}_e(b)=frac{B}{A}+frac{b}{A}>frac{B}{A}=p, $$
that is, the effective price paid by the trader is greater than the spot price. Note also that the difference between them is
$$ {p}_e(b)-p=frac{b}{A}, $$
(2.5)

which is small if the deposited amount b is small with respect to the balance A of token X. This means that for pools with large liquidity, this difference will be small.

The spot price can then be interpreted as the price the liquidity pool offers traders at a particular moment. However, as we have seen, traders will always have to pay more than that. The difference between the amount b that a trader pays (or deposits) and the amount that the trader would have paid if they had bought an amount a of the asset at a price equal to the spot price p is called price impact1 and is given by
$$ 	extrm{Price}kern0.17em 	extrm{impact}=b- ap. $$
Note that
$$ 	extrm{Price}kern0.17em 	extrm{impact}=aleft(frac{b}{a}-p
ight)=aleft({p}_e(b)-p
ight)=frac{ab}{A} $$

by Equation 2.5.

In Figure 2-2, we show the geometric interpretations of both the spot price and the price impact. With the previous notations, we mentioned before that when the pool state is (A, B), the spot price p is equal to $$ frac{B}{A} $$, which coincides with the slope of the line segment that goes from (0,0) to (A, B) (this is the dotted segment of Figure 2-2). Note also that if the state of the pool before a trade is P = (A, B) and the state of the pool after that trade is P = (A, B) = (A − a, B + b), then the effective price that the trader paid for each unit of token X is
$$ {p}_e=frac{B^{hbox{'}}-B}{A-{A}^{hbox{'}}}. $$
Figure 2-2

Geometric interpretation of the spot price and the price impact

Since the point (A, B) belongs to the curve xy = L2, we can regard B as a function of A, and hence, pe can also be thought of as a function of A (recall that A and B are fixed). Hence, we obtain that the spot price p is given by
$$ p=underset{A^{hbox{'}}	o A}{lim }{p}_eleft({A}^{hbox{'}}
ight)=underset{A^{hbox{'}}	o A}{lim}frac{B^{hbox{'}}-B}{A-{A}^{hbox{'}}}=-underset{A^{hbox{'}}	o A}{lim}frac{B^{hbox{'}}-B}{A^{hbox{'}}-A}, $$
which is the opposite of the slope of the tangent line to the curve xy = L2 at the point (A, B). Note that this slope can also be computed by isolating y from the equation xy = L2 to obtain
$$ y=frac{L^2}{x}, $$
from where we get that the derivative of y with respect to x is
$$ {y}^{hbox{'}}=-frac{L^2}{x^2}, $$
and thus, the slope of the tangent line to the curve xy = L2 at the point (A, B) is
$$ -frac{L^2}{A^2}=-frac{AB}{A^2}=-frac{B}{A}=-p, $$

as expected. We can also see this in Figure 2-2.

Regarding the price impact, since the slope of the tangent line is the opposite of the spot price p, it follows that in Figure 2-2, q = ap, and hence, q is the amount of token Y that would have been paid if the whole trade had been executed at the spot price. Therefore, we obtain that the price impact s is equal to bq, and hence, it can be interpreted as the length of the dashed segment labelled r of Figure 2-2.

Example 2.1. Consider a Uniswap v2 liquidity pool with no fees that has 200 ETH and 800,000 USDC. Note that the spot price of ETH in terms of USDC is
$$ p=frac{800000}{200}=4000. $$
Suppose that a trader deposits 200,000 USDC into the pool so as to receive ETH. Applying Equation 2.3, we obtain that the amount of ETH that the trader receives is
$$ a=frac{200cdot 200,000}{800,000+200,000}=40. $$
Hence, the effective price paid by the trader is
$$ frac{200,000}{40}=5000, $$

which is more than the spot price p. This means that the trader paid 5,000 USDC for each ETH.

Since the trader bought 40 ETH and the spot price was 4,000, the trader would have expected to need to deposit 40×4,000 USDC, that is, 160,000 USDC, but instead, 200,000 USDC were required for the transaction. The difference
$$ 200,000-160,000=40,000;	extrm{USDC} $$

is the price impact of the trade, which can be seen as a loss for the trader.

2.1.2 Accounting for Fees

Equations 2.2 and 2.3 are valid if the liquidity pool has no fees, but in general, traders are charged a fee for trading. The collected fees are added to the pool reserves and then paid to liquidity providers in a way that is proportional to the deposits they have made.

Uniswap v2 charges fees on the way in. That means that when a trader wants to buy or sell assets, first, the protocol charges the fee on the amount that the trader deposits, and then the remaining amount is actually traded using the previous arguments, as Figure 2-3 shows.
Figure 2-3

Trading mechanism with fees

We will see how the mathematics for this works. Let A and B be the balances of tokens X and Y in a Uniswap v2 liquidity pool. Let ϕ ∈ [0, 1) be the pool fee. For example, if the pool fee is 0.3%, then ϕ = 0.003. Let L be the liquidity parameter of the pool. Thus, AB = L2. Suppose that a trader deposits an amount b of token Y and receives an amount a of token X. Since the fee is charged on the way in, we may assume that the deposited amount is (1 − ϕ)b and proceed as if the pool had no fees. Hence, the amounts a and b satisfy the following equation:
$$ left(A-a
ight)left(B+left(1-phi 
ight)b
ight)={L}^2. $$
Thus,
$$ {displaystyle egin{array}{ll}{L}^2& =left(A-a
ight)left(B+left(1-phi 
ight)b
ight)\ {}& = AB+Aleft(1-phi 
ight)b- aB-aleft(1-phi 
ight)b\ {}& ={L}^2+Aleft(1-phi 
ight)b- aB-aleft(1-phi 
ight)bend{array}} $$
and hence,
$$ Aleft(1-phi 
ight)b- aB-aleft(1-phi 
ight)b=0. $$
(2.6)
Isolating a, we obtain that
$$ a=frac{Aleft(1-phi 
ight)b}{B+left(1-phi 
ight)b} $$
(2.7)

This means that if the trader deposits an amount b of token Y, they will receive an amount $$ frac{Aleft(1-phi 
ight)b}{B+left(1-phi 
ight)b} $$ of token X.

Similarly, isolating b from Equation 2.6, we obtain that
$$ b=frac{aB}{left(1-phi 
ight)left(A-a
ight)} $$
(2.8)

This means that in order to receive an amount a of token X, the trader has to deposit an amount $$ frac{aB}{left(1-phi 
ight)left(A-a
ight)} $$ of token Y.

Equations 2.7 and 2.8 are covered by the portion of the Uniswap v2 code2 given in Listing 2-1, applying the following translation between the variables of the code and our notation:

reserveIn = B,

reserveOut = A,

amountIn = b,

amountOut = a,

Observe that the actual code (given in Listing 2-1) employs a trading fee of 0.3% and performs a multiplication by 1,000 that is cancelled when the quotient is computed.
// given an input amount of an asset and pair
↪  reserves, returns the maximum output amount of the
↪  other asset
function getAmountOut(uint amountIn, uint reserveIn,
↪  uint reserveOut) internal pure returns (uint
↪  amountOut) {
    require(amountIn > 0, 'UniswapV2Library:
↪  INSUFFICIENT_INPUT_AMOUNT');
    require(reserveIn > 0 && reserveOut > 0,
↪  'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
    uint amountInWithFee = amountIn.mul(997);
    uint numerator = amountInWithFee.mul(reserveOut);
    uint denominator =
↪  reserveIn.mul(1000).add(amountInWithFee);
    amountOut = numerator / denominator;
}
// given an output amount of an asset and pair
↪  reserves, returns a required input amount of the
↪  other asset
function getAmountIn(uint amountOut, uint reserveIn,
↪  uint reserveOut) internal pure returns (uint
↪  amountIn) {
    require(amountOut > 0, 'UniswapV2Library:
↪  INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0,
↪  UniswapV2Library: INSUFFICIENT_LIQUIDITY');
    uint numerator =
↪  reserveIn.mul(amountOut).mul(1000);
    uint denominator =
↪  reserveOut.sub(amountOut).mul(997);
    amountIn = (numerator / denominator).add(1);
}
Listing 2-1

Functions getAmountOut and GetAmountIn of the smart contract of Uniswap v2

It is important to point out that after each transaction, the pool reserves are updated in the following way:
$$ {displaystyle egin{array}{ll}& {A}^{hbox{'}}=A-a\ {}& {B}^{hbox{'}}=B+b.end{array}} $$
Thus, the liquidity parameter is also updated to a value L such that L > 0 and
$$ {left({L}^{hbox{'}}
ight)}^2=left(A-a
ight)left(B+b
ight)= AB+ Ab- aB- ab={L}^2+left(A-a
ight)b- aB $$
and since from Equation 2.6 we have that aB = (A − a)(1 − ϕ)b, we obtain that
$$ {left({L}^{hbox{'}}
ight)}^2={L}^2+phi bleft(A-a
ight). $$
(2.9)

Note that (L)2 > L2 if ϕ > 0. That is, if the liquidity pool has nonzero fees, then the liquidity parameter will increase a bit after each transaction.

Observe also that the spot price changes after the transaction. In this case, the new spot price is
$$ {p}^{hbox{'}}=frac{B+b}{A-a} $$
In summary, if the liquidity pool has no fees, its liquidity parameter remains the same after each trade (this is L = L with the previous notations). In addition, if a trader deposits an amount b of token Y and receives an amount a of token X, and as above, A and B are the pool balances before the trade and A and B are the pool balances after the trade, then the point (A, B) is located on the same multiplicative inverse curve as the point (A, B), as Figure 2-4.
Figure 2-4

Change of the pool state when trading in a pool with no fees

On the other hand, if the liquidity pool has a nonzero fee ϕ, then the liquidity parameter increases after each trade (this is L > L with the previous notations). If the pool reserves before a trade are A and B, and a trader deposits an amount b of token Y and receives an amount a of token X, then

$$ left(A-a
ight)left(B+bleft(1-phi 
ight)
ight)={L}^2, $$
as we showed before. That is, the point Q = (A − a, B + b(1 − ϕ)) is located on the curve defined by the liquidity parameter L, as we can see in Figure 2-5 (curve in solid line). Observe that the updated pool balances are defined by the point P = (A − a, B + b) and that (A − a, B + b) = (A − a, B + b(1 − ϕ)) + (0, ). Thus, the point P is now located on a new multiplicative inverse curve (the dashed one in Figure 2-5).
Figure 2-5

Change of the pool state when trading in a pool with fees

Note that the absolute value of the slope of the segment between the points P = (A,B) and P = (A − a, B + b) shown in Figure 2-5 is exactly the deposited amount of token Y per unit of token X, that is, the effective price paid by the trader.

2.2 Impact of the Trades on the Price

When a trade is executed, the balances of the tokens in the pool change, and as a consequence of the trade, the spot price also changes. In this section, we will perform several analyses to show how the spot price varies when trades occur.

2.2.1 A Simple Example

We will give first a simple example that shows how the spot price of a token increases after a trader purchases an amount of it.

Example 2.2 (Shift in the average buy price). Consider a Uniswap v2 pool with ETH and USDC, having a fee of 0.3%. Suppose that the pool has reserves of 10,000 ETH and 40,000,000 USDC. Let A = 10,000 and B = 40,000,000. Note that the spot price is 4,000 USDC/ETH.

Suppose that a trader wants to buy an amount of 500 ETH from the pool. To find out the amount of USDC that the trader has to deposit in order to obtain 500 ETH, we apply Equation 2.8 with a = 500:
$$ {b}_1=frac{acdot B}{left(1-phi 
ight)cdot left(A-a
ight)}approx 2,111,598;	extrm{USDC}. $$
Hence, the trader has to deposit approximately 2,111,598 USDC in order to obtain 500 ETH. This is equivalent to an average price of approximately 4,223 USDC/ETH. If the trader wants to buy 500 ETH again, we need to compute the updated balances (after the first trade):
$$ {displaystyle egin{array}{ll}{A}^{hbox{'}}=9,500& left(	extrm{balance}kern0.17em 	extrm{of}; ETH
ight),\ {}{B}^{hbox{'}}=42,111,598& left(	extrm{balance}kern0.17em 	extrm{of}kern0.17em 	extrm{USDC}
ight)end{array}} $$
Again, to compute the amount of USDC that the trader has to deposit in order to obtain 500 ETH, we apply Equation 2.8 with a = 500 and the new balances:
$$ {b}_2=frac{acdot {B}^{hbox{'}}}{left(1-phi 
ight)cdot left({A}^{hbox{'}}-a
ight)}approx 2,346,573kern1em 	extrm{USDC}. $$

This means that the trader has to deposit approximately 2,346,573 USDC in order to obtain 500 ETH. This is equivalent to an average price of roughly 4,693 USDC/ETH. We find that the price of ETH in terms of USDC has increased due to the fact that the amounts of USDC and ETH have respectively increased and decreased reflecting a higher demand for ETH and thus a higher price for ETH in terms of USDC.

Observe that in general, and with the previous notations, if we buy an amount a of token X, the total price that we have to pay is
$$ {b}_1=frac{acdot B}{left(1-phi 
ight)cdot left(A-a
ight)}. $$
If after that we buy an amount a of token X again, the new total price that we have to pay is
$$ {b}_2=frac{acdot left(B+{b}_1
ight)}{left(1-phi 
ight)cdot left(A-2a
ight)}. $$
Note that
$$ {b}_1=frac{acdot B}{left(1-phi 
ight)cdot left(A-a
ight)}<frac{acdot left(B+{b}_1
ight)}{left(1-phi 
ight)cdot left(A-2a
ight)}={b}_2 $$

since B < B + b1 and A − a > A − 2a.

2.2.2 Analysis of Two Consecutive Trades

We will now extend the previous example to study if there is any difference between buying 500 ETH in only one trade and buying 500 ETH in two consecutive trades of 250 ETH each.

Example 2.3 (ETH purchase price comparison). Consider again a liquidity pool of 10,000 ETH and 40,000,000 USDC. Let A = 10, 000 and B = 40,000,000.

Suppose first that the pool has no fees. Using Equation 2.8, we obtain that in order to receive 500 ETH, we have to deposit
$$ {b}_0=frac{acdot B}{A-a}=frac{500cdot 40,000,000}{10,000-500}approx 2,105,263;	extrm{USDC}. $$
On the other hand, for two consecutive purchases of 250 ETH each, we need to pay
$$ {displaystyle egin{array}{ll}{b}_1&amp; =frac{acdot B}{A-a}=frac{250cdot 40,000,000}{10,000-250}approx 1,025,641;	extrm{USDC}kern0.17em 	extrm{and}\ {}{b}_2&amp; =frac{acdot {B}^{hbox{'}}}{A^{hbox{'}}-a}=frac{250cdot 41,025,641}{9,750-250}approx 1,079,622;	extrm{USDC}end{array}} $$
(note that the values of A and B are updated to A = 9,750 and B = 41,025,641 after the first purchase). We observe that
$$ {b}_0={b}_1+{b}_2, $$

that is, the amount of USDC that has to be paid in order to buy 500 ETH in one step is the same as the amount of USDC needed to buy 500 ETH in two consecutive steps of 250 ETH each.

This fact has an interesting graphical interpretation. Since the pool has no fees, the liquidity parameter L remains constant, and thus, the points representing the different states of the pool belong to the same multiplicative inverse curve. Therefore, the point obtained after subtracting 500 from the ETH coordinate of the initial state is the same as the point obtained by subtracting 250 from the ETH coordinate of the initial state two times, as Figure 2-6 shows.
Figure 2-6

Comparison between making a trade in one step and dividing it into two trades in a pool with no fees

Now suppose that the pool has a fee of 0.3%. From the previous example, we see that in order to obtain 500 ETH, we have to pay approximately 2,111,598 USDC. On the other hand, for two consecutive buys of 250 ETH, we need to deposit the following amounts of USDC:
$$ {displaystyle egin{array}{ll}{b}_1&amp; =frac{acdot B}{left(1-phi 
ight)cdot left(A-a
ight)}=frac{250cdot 40,000,000}{0.997cdot left(10,000-250
ight)}approx 1,028,727\ {}{b}_2&amp; =frac{acdot left(B+{b}_1
ight)}{left(1-phi 
ight)cdot left(A-2a
ight)}approx frac{250cdot 41,028,727}{0.997cdot left(10,000-500
ight)}approx 1,082,952.end{array}} $$

Hence, in order to buy 500 ETH in two steps, we need to pay approximately 1,028,727 + 1,082,952 = 2,111,679 USDC, which is more than the 2,111,598 USDC needed to buy 500 ETH in one step.

Here, we can make another interesting graphical interpretation. Since the pool has fees, the value of the liquidity parameter L increases after the first purchase (see Equation 2.9). Thus, the points representing the different states of the pool belong to different multiplicative inverse curves. In Figure 2-7, we can see the difference between buying 500 ETH in only one trade and buying 500 ETH in two steps.
Figure 2-7

Comparison between making a trade in one step and dividing it into two trades in a pool with fees

The following proposition formalizes and generalizes the results of the previous example.

Proposition 2.1. Consider a liquidity pool with two tokens X and Y and with fee ϕ ∈ [0, 1). Let A be the balance of token X in the pool and let B be the balance of token Y in the pool. Let a1, a2 > 0 such that a1 + a2 < A. Let b0 be the amount of token Y needed to buy a1 + a2 tokens X, let b1 be the amount of token Y needed to buy a1 tokens X, and let b2 be the amount of token Y needed to buy a2 tokens X after a1 tokens X have been purchased. Then b0 ≤ b1 + b2 and the equality holds if and only if ϕ = 0.

Proof. Let A = A − a1 and A = A − a1 − a2. From Equation 2.8, we obtain that
$$ {displaystyle egin{array}{ll}{b}_0&amp; =frac{left({a}_1+{a}_2
ight)B}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}\ {}{b}_1&amp; =frac{a_1B}{left(1-phi 
ight){A}^{hbox{'}}}kern1em \ {}{b}_2&amp; =frac{a_2left(B+{b}_1
ight)}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}end{array}} $$
We have that
$$ {b}_1+{b}_2-{b}_0=frac{a_1B}{left(1-phi 
ight){A}^{hbox{'}}}+frac{a_2left(B+{b}_1
ight)}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}-frac{left({a}_1+{a}_2
ight)B}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}} $$
$$ =frac{1}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}left(frac{a_1{BA}^{hbox{'}hbox{'}}}{A^{hbox{'}}}+{a}_2left(B+{b}_1
ight)-left({a}_1+{a}_2
ight)B
ight) $$
$$ =frac{1}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}left(frac{a_1Bleft(A-{a}_1-{a}_2
ight)}{A-{a}_1}+{a}_2{b}_1-{a}_1B
ight) $$
$$ =frac{1}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}left({a}_1B-frac{a_1{a}_2B}{A-{a}_1}+{a}_2{b}_1-{a}_1B
ight) $$
$$ =frac{1}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}left({a}_2{b}_1-frac{a_1{a}_2B}{A-{a}_1}
ight) $$
$$ =frac{1}{left(1-phi 
ight){A}^{hbox{'}hbox{'}}}left(frac{a_1{a}_2B}{left(1-phi 
ight){A}^{hbox{'}}}-frac{a_1{a}_2B}{A^{hbox{'}}}
ight) $$
$$ =frac{a_1{a}_2B}{left(1-phi 
ight){A}^{hbox{'}}{A}^{hbox{'}hbox{'}}}left(frac{1}{left(1-phi 
ight)}-1
ight) $$
$$ =frac{a_1{a}_2B}{left(1-phi 
ight){A}^{hbox{'}}{A}^{hbox{'}hbox{'}}}frac{phi }{left(1-phi 
ight)}. $$

Thus, b1 + b2 − b0 ≥ 0 and b1 + b2 − b0 = 0 if and only if ϕ = 0. The result follows.                          □

2.2.3 Impact of the Trade Size on the Average Purchase Price

In practice, traders often buy many tokens at once, and as we have seen in Example 2.2, every token costs more than the previous one. We will now analyze this in more detail. Consider a liquidity pool with tokens X and Y and trading fee ϕ. Let A and B be the balances of tokens X and Y in the pool. Suppose that a trader deposits an amount b of token Y and receives an amount a of token X (hence, they buy token X ).

Applying Equation 2.8, we obtain that the amount of token Y that the trader has to deposit (pay) per token X that the trader receives is given by the following formula:
$$ frac{b}{a}=frac{B}{left(1-phi 
ight)left(A-a
ight)} $$
(2.10)
This can be interpreted as the average purchase price in token Y per unit of token X. The total amount of token Y that has to be deposited is given by Equation 2.8:
$$ b=frac{aB}{left(1-phi 
ight)left(A-a
ight)} $$

We will study these formulae in the following example.

Example 2.4 (Purchase price impact). Consider a liquidity pool with B = 40,000,000 USDC and A = 10,000 ETH, having a fee of 0.3%. In Figure 2-8, we plot the price that the trader has to pay for each unit of ETH (in terms of USDC) as a function of the amount of ETH they want to buy (Equation 2.10). Note that it is an increasing function. In Figure 2-8, we also plot the amount of USDC deposited as a function of the received amount a of ETH (Equation 2.8). We observe that it is a convex function.
Figure 2-8

Price impact: impact of the traded amount on the purchase price

2.2.4 Impact of the Trade Size on the Average Sell Price

We will now make a similar analysis from a seller’s perspective. As in the previous subsection, consider a liquidity pool with reserve amounts A of token X and B of token Y, and suppose that a trader deposits an amount b of token Y and receives an amount a of token X (and so, the trader sells token X). Applying Equation 2.7, we obtain that the amount of token X that the trader receives per token Y that the trader deposits is given by the following formula:
$$ frac{a}{b}=frac{Aleft(1-phi 
ight)}{B+left(1-phi 
ight)b}. $$
(2.11)
This can be interpreted as the average sell price in token X per unit of token Y. The total amount of token X received is given by Equation 2.7:
$$ a=frac{Aleft(1-phi 
ight)b}{B+left(1-phi 
ight)b}. $$

We will study these formulae in the following example.

Example 2.5 (Sell price impact). Consider a liquidity pool with reserves A = 40,000,000 USDC and B = 10,000 ETH, having a 0.3% fee. Suppose that a trader sells ETH; that is, the trader deposits an amount b of ETH and receives an amount a of USDC. Using Equation 2.11, we plot in Figure 2-9 the price of each unit of ETH in terms of USDC as a function of the deposited amount b of ETH. Observe that it is a decreasing function. In Figure 2-9, we also plot the amount of USDC received as a function of the deposited amount b of ETH (Equation 2.7). We observe that it is a concave function.
Figure 2-9

Price impact: impact of the amount traded on the sell price

2.2.5 Impact of the Trade Size on the Price Growth Ratio

We will now study the impact of the trade size on the growth of the effective price paid by a trader. To this end, consider a Uniswap v2 liquidity pool with tokens X and Y and a trading fee of 0.3%. Let A and B be the balances of the tokens X and Y, respectively. Note that the current spot price is $$ p=frac{B}{A} $$. We want to compute the growth of the spot price after a trader buys an amount a of token X. Note that the amount of token Y the trader has to deposit to buy an amount a of token X is given by Equation 2.8. The updated balances after the trade are
$$ {displaystyle egin{array}{ll}{A}^{hbox{'}}&amp; =A-a,\ {}{B}^{hbox{'}}&amp; =B+frac{aB}{left(1-phi 
ight)left(A-a
ight)},end{array}} $$
and hence, the spot price after the trade is
$$ {p}^{hbox{'}}=frac{B^{hbox{'}}}{A^{hbox{'}}}. $$
Let $$ r=frac{a}{A} $$; that is, r is the fraction of token X that the trader is buying (with respect to the balance of token X in the pool). Using the fact that $$ p=frac{B}{A} $$, we can write the updated spot price p in terms of p and r as follows:
$$ {displaystyle egin{array}{ll}{p}^{hbox{'}}&amp; =frac{B+frac{aB}{left(1-phi 
ight)left(A-a
ight)}}{A-a}=frac{p+frac{p}{left(1-phi 
ight)left({r}^{-1}-1
ight)}}{1-r}\ {}&amp; =pcdot frac{1+left({r}^{-1}-1
ight)left(1-phi 
ight)}{left(1-r
ight)left({r}^{-1}-1
ight)left(1-phi 
ight)}.end{array}} $$
Hence, the price growth ratio is
$$ frac{p^{hbox{'}}}{p}=frac{1+left({r}^{-1}-1
ight)left(1-phi 
ight)}{left(1-r
ight)left({r}^{-1}-1
ight)left(1-phi 
ight)} $$
and it does not depend on the state of the pool. The percentage of increment in the price is given by $$ left(frac{p^{hbox{'}}}{p}-1
ight)cdot 100 $$. In Figure 2-10, we plot (in solid line) the price increment as a function of the fraction r of token X bought (in percentage) in the range (0, 10], and we compare it with a linear function.
Figure 2-10

Percentage of price increment as a function of the percentage of token X bought

In Figure 2-11, we plot again (in solid line) the price increment as a function of the fraction r of token X bought, this time in the range (0, 30], and the same linear function as before.
Figure 2-11

Percentage of price increment as a function of the percentage of token X bought

2.3 Providing Liquidity

In this section, we will explain how liquidity providers can add liquidity to a Uniswap v2 pool and how they can withdraw the liquidity they have deposited. We will also analyze the possible impermanent losses that a liquidity provider might face. In the last part of this section, we will explain how to set a fair price for a liquidity provider’s position.

2.3.1 Minting LP Tokens

When a liquidity provider wants to provide liquidity to a Uniswap v2 pool, they need to deposit amounts of tokens X and Y that are in a proportion defined by the state of the pool. By doing so, they will be given specific tokens, called liquidity pool tokensor LP tokenswhich represent the share of the pool they own. The liquidity provider will also earn trading fees according to that share. Note that that share will vary when either new liquidity providers enter the pool or existing ones leave the pool.

We will see now how the process for providing liquidity works. Consider a Uniswap v2 liquidity pool with tokens X and Y. Let A and B be the corresponding amounts of tokens X and Y in the pool. If a liquidity provider wants to add liquidity to the pool, they have to deposit an amount a of token X and an amount b of token Y satisfying
$$ frac{b}{a}=frac{B}{A}, $$
(2.12)

that is, the amounts of tokens X and Y that the liquidity provider deposits have to be in the same proportion as that of the pool reserves. Observe that the spot price after the deposit is $$ frac{B+b}{A+a} $$, which is the same as the original spot price $$ frac{B}{A} $$ since $$ frac{b}{a}=frac{B}{A} $$ (and thus, A(B + b) = B(A + a)).

When liquidity providers add liquidity to a pool, they are sent LP tokens in return. The LP tokens act as a receipt of the liquidity providers’ provision and represent their share of the liquidity in the pool. The total amount of LP tokens within a pool is dynamic. The AMM keeps track of how many LP tokens it has created and given in return for liquidity. When a new user provides liquidity, the AMM mints an appropriate amount of LP tokens, which is sent to the new liquidity provider, representing the proportion of liquidity that they have provided (see Figure 2-12). In this way, the relative shares of the pool for both previous and new liquidity providers are kept accurate.
Figure 2-12

Minting LP tokens

Concretely, consider a Uniswap v2 liquidity pool with tokens X and Y, and let A and B be the corresponding amounts of tokens X and Y in the pool. Let M be the amount of existent LP tokens. Suppose that a liquidity provider wants to add liquidity to the pool by depositing an amount a of token X and an amount b of token Y. Recall that the amounts a and b must satisfy that
$$ frac{b}{a}=frac{B}{A} $$
or equivalently,
$$ frac{a}{A}=frac{b}{B} $$
Let
$$ q=frac{a}{A}. $$

The new liquidity provider will receive an amount qM of LP tokens. This means that an amount qM of LP tokens will be newly minted and that the total amount of existent LP tokens will be updated to M + qM = (1 + q)M.

The Uniswap v2 AMM does not require the liquidity provider to find the exact amounts to deposit. Instead, given amounts CX of token X and CY of token Y that the liquidity provider has available for the deposit, the protocol computes the maximum amounts of tokens X and Y that the liquidity provider can deposit to preserve the pool proportions, and gives the remaining amount of either token X or token Y that was not deposited back to the liquidity provider, together with the LP tokens that correspond to the deposit. This feature is particularly useful due to the ever-changing nonpredictable pool state arising from the fact that the AMM is decentralized. Indeed, if a liquidity provider computes at a certain moment the amounts that they need to deposit, by the time the transaction is processed, the pool state could be different, since other pool transactions could have been processed first, and hence, the amounts needed for the liquidity deposit might have changed.

We will see now how the liquidity deposit works. As before, let A and B be the corresponding amounts of tokens X and Y in the pool and let M be the amount of existent LP tokens. We will consider three cases.
  • Case 1: $$ frac{C_Y}{C_X}=frac{B}{A} $$.

In this case, we have that the amounts CX and CY are already in the right proportions, so we define $$ q=frac{C_X}{A} $$. Then, the liquidity provider will receive an amount qM of LP tokens. Note that
$$ qM=frac{C_X}{A}M=frac{C_Y}{B}M. $$
  • Case 2: $$ frac{C_Y}{C_X}&gt;frac{B}{A} $$

In this case, we will find an amount ΔY > 0 such that
$$ frac{C_Y-{Delta}_Y}{C_X}=frac{B}{A}. $$
Clearly,
$$ frac{C_Y-{Delta}_Y}{C_X}=frac{B}{A}iff {C}_Y-{Delta}_Y=frac{BC_X}{A}iff {Delta}_Y={C}_Y-frac{BC_X}{A}. $$

Since $$ frac{C_Y}{C_X}&gt;frac{B}{A} $$, we obtain that $$ {C}_Y&gt;frac{BC_X}{A} $$. Then $$ {C}_Y-frac{BC_X}{A}&gt;0 $$.

Thus, we take $$ {Delta}_Y={C}_Y-frac{BC_X}{A} $$, and hence, $$ frac{C_Y-{Delta}_Y}{C_X}=frac{B}{A} $$. This means that the amounts CX and CY − ΔY are in the right proportions. We define then $$ q=frac{C_X}{A} $$. Hence, the liquidity provider will be given back an amount ΔY of token Y and will receive an amount qM of LP tokens. Note that
$$ qM=frac{C_X}{A}M=frac{C_Y-{Delta}_Y}{B}M&lt;frac{C_Y}{B}M. $$
  • Case 3: $$ frac{C_Y}{C_X}&lt;frac{B}{A} $$

In this case, we will find an amount ΔX > 0 such that
$$ frac{C_Y}{C_X-{Delta}_X}=frac{B}{A}. $$
Clearly,
$$ frac{C_Y}{C_X-{Delta}_X}=frac{B}{A}iff {C}_X-{Delta}_X=frac{AC_Y}{B}iff {Delta}_X={C}_X-frac{AC_Y}{B}. $$

Since $$ frac{C_Y}{C_X}&lt;frac{B}{A} $$, we obtain that $$ frac{AC_Y}{B}&lt;{C}_X $$. Then $$ {C}_X-frac{AC_Y}{B}&gt;0 $$.

Thus, we take $$ {Delta}_X={C}_X-frac{AC_Y}{B} $$ and hence $$ frac{C_Y}{C_X-{Delta}_X}=frac{B}{A} $$. This means that the amounts CX − ΔX and CY are in the right proportions. We define then $$ q=frac{C_X-{Delta}_X}{A} $$. Hence, the liquidity provider will be given back an amount ΔX of token X and will receive an amount qM of LP tokens. Note that
$$ qM=frac{C_Y}{B}M=frac{C_X-{Delta}_X}{A}M&lt;frac{C_X}{A}M. $$
Summing up, in any case, the liquidity provider will receive an amount of LP tokens equal to
$$ min left{frac{C_X}{A}M,frac{C_Y}{B}M
ight}. $$

In the code given in Listing 2-2, we can find the previous formula.3 The translation between the variables of the code and our notation is

totalsupply = M,

amount0 = CX,

amount1 = CY,

_eserve0 = A,

_eserve1 = B,

liquidity = qM.

function mint(address to) external lock returns (uint
↪  liquidity) {
    (uint112 _reserve0, uint112 _reserve1,) =
↪  getReserves(); // gas savings
    uint balance0 =
↪  IERC20(token0).balanceOf(address(this));
    uint balance1 =
↪  IERC20(token1).balanceOf(address(this));
    uint amount0 = balance0.sub(_reserve0);
    uint amount1 = balance1.sub(_reserve1);
    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply; // gas savings,
↪  must be defined here since totalSupply can update
↪  in _mintFee
    if (_totalSupply == 0) {
        liquidity =
↪  Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
        _mint(address(0), MINIMUM_LIQUIDITY); //
↪  permanently lock the first MINIMUM_LIQUIDITY
↪  tokens
    } else {
        liquidity = Math.min(amount0.mul(_totalSupply)
↪  / _reserve0, amount1.mul(_totalSupply) /
↪  _reserve1);
    }
    require(liquidity > 0, 'UniswapV2:
↪  INSUFFICIENT_LIQUIDITY_MINTED');
    _mint(to, liquidity);
    _update(balance0, balance1, _reserve0, _reserve1);
    if (feeOn) kLast = uint(reserve0).mul(reserve1);
↪  // reserve0 and reserve1 are up-to-date
    emit Mint(msg.sender, amount0, amount1);
}
Listing 2-2

mint function of the smart contract of Uniswap v2

Starting the Pool

Suppose that a liquidity provider starts a liquidity pool adding amounts a and b of tokens X and Y, respectively. When starting the pool, the amounts a and b can be in any proportion since there are no balances to compare with. However, Uniswap v2 requires the initial amounts a and b to satisfy that $$ sqrt{acdot b}&gt;1000 $$. In this case, the liquidity provider will receive an amount of LP tokens defined by
$$ LP;	extrm{tokens}kern0.17em 	extrm{received};left(	extrm{start}
ight)=sqrt{acdot b}-1000. $$
In addition, 1,000 LP tokens will be burnedthat is, sent to a special address where the funds can never be retrieved by anyone. It is worth mentioning that this amount of 1,000 is added to the totalSupply variable, as it can be seen from the code given in Listing 2-3. Hence, the totalSupply variable will always be bounded below by 1,000. As it is explained in the Uniswap v2 documentation,4 this is done in order to ameliorate rounding errors, and the burned amount will generally represent a negligible value.
uint public constant MINIMUM_LIQUIDITY = 10**3;
// ... //
function mint(address to) external lock returns (uint liquidity) {
// ... //
    if (_totalSupply == 0) {
        liquidity =
↪  Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
    _mint(address(0), MINIMUM_LIQUIDITY); //
↪  permanently lock the first MINIMUM_LIQUIDITY
↪  tokens
    } else {
        liquidity = Math.min(amount0.mul(_totalSupply)
↪  / _reserve0, amount1.mul(_totalSupply) /
↪  _reserve1);
    }
    require(liquidity > 0, 'UniswapV2:
↪  INSUFFICIENT_LIQUIDITY_MINTED');
// ... //
function _mint(address to, uint value) internal {
    totalSupply = totalSupply.add(value);
    balanceOf[to] = balanceOf[to].add(value);
    emit Transfer(address(0), to, value);
}
Listing 2-3

Portion of the mint function of the smart contract of Uniswap v2

2.3.2 Burning LP Tokens

Liquidity providers can also remove liquidity from the pool. In this case, they have to send their LP tokens to the pool, and they will be given certain amounts of the pool tokens according to their share of the pool, as Figure 2-13.
Figure 2-13

Burning LP tokens

We will now explain in detail how this works. Consider a Uniswap v2 liquidity pool with tokens X and Y. Let A and B be the corresponding amounts of tokens X and Y in the pool. Let M be the amount of existent LP tokens and let m be the amount of LP tokens that the liquidity provider wants to redeem. Clearly, the amount m of LP tokens corresponds to a share $$ frac{m}{M} $$ of the pool. Therefore, in exchange for the amount m of LP tokens, the liquidity provider will receive an amount $$ frac{m}{M}A $$ of token X and an amount $$ frac{m}{M}B $$ of token Y. The amount m of LP tokens deposited by the liquidity provider will be burned, and the amount of existent LP tokens will be updated to Mm. Clearly, the pool reserves will also be updated to $$ A-frac{m}{M}A $$ for token X and $$ B-frac{m}{M}B $$ for token Y.

The formula for the amount of each token that the liquidity provider has to receive when redeeming LP tokens is covered by the burn function of Uniswap v2,5 which is given in Listing 2-4, with the following translation between the variables of the code and our notation:

// this low-level function should be called from a
↪  contract which performs important safety checks
function burn(address to) external lock returns (uint
↪  amount0, uint amount1) {
    (uint112 _reserve0, uint112 _reserve1,) =
↪  getReserves(); // gas savings
    address _token0 = token0;
↪  // gas savings
    address _token1 = token1;
↪  // gas savings
    uint balance0 =
↪  IERC20(_token0).balanceOf(address(this));
    uint balance1 =
↪  IERC20(_token1).balanceOf(address(this));
    uint liquidity = balanceOf[address(this)];
    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply; // gas savings,
↪  must be defined here since totalSupply can update
↪  in _mintFee
    amount0 = liquidity.mul(balance0) / _totalSupply;
↪  // using balances ensures pro-rata distribution
    amount1 = liquidity.mul(balance1) / _totalSupply;
↪  // using balances ensures pro-rata distribution
    require(amount0 > 0 && amount1 > 0, 'UniswapV2:
↪  INSUFFICIENT_LIQUIDITY_BURNED');
    _burn(address(this), liquidity);
    _safeTransfer(_token0, to, amount0);
    _safeTransfer(_token1, to, amount1);
    balance0 =
↪  IERC20(_token0).balanceOf(address(this));
    balance1 =
↪  IERC20(_token1).balanceOf(address(this));
    _update(balance0, balance1, _reserve0, _reserve1);
    if (feeOn) kLast = uint(reserve0).mul(reserve1);
↪  // reserve0 and reserve1 are up-to-date
    emit Burn(msg.sender, amount0, amount1, to);
}
Listing 2-4

burn function of the smart contract of Uniswap v2

Distribution of Fees

We explained before how traders are charged a fee on the way in, meaning that a certain amount of the token they deposit is kept as a fee and the remaining amount is actually traded (see Figure 2-3). That fee is not shared among the liquidity providers immediately, since doing that would be not only impractical but also very expensive in terms of gas fees. Instead, the fee is added to the pool so that the whole value of the pool increases and thus the value of each liquidity provider’s share increases as well. In this way, when the liquidity provider decides to redeem their LP tokens, they obtain an added value from the fees that have accumulated in the pool (see Figure 2-14).
Figure 2-14

Visualizing how liquidity providers earn trading fees

2.3.3 Pool Value and Impermanent Loss

Consider a Uniswap v2 liquidity pool with tokens X and Y. Let A and B be the balances of tokens X and Y, respectively. From Equation 2.4, we know that the spot price of token X in terms of token Y is $$ frac{B}{A} $$, and hence, the value of the amount A of token X that the pool has in terms of token Y is $$ frac{B}{A}cdot A $$. And since the value of the amount B of token Y in the pool is B, we obtain that the total value of the pool in terms of token Y is
$$ frac{B}{A}cdot A+B=2B. $$
Suppose that a liquidity provider owns a share ρ of all the existent LP tokens. If they decide to redeem their LP tokens, they will receive an amount ρA of token X and an amount ρB of token Y. Therefore, the value of their position in terms of token Y is
$$ frac{B}{A}cdot 
ho A+
ho B=
ho left(frac{B}{A}cdot A+B
ight)=
ho cdot 2B, $$

that is, a fraction ρ of the whole pool value, as expected.

Computing Impermanent Loss Without Fees

Consider a Uniswap v2 liquidity pool with no fees having 9 ETH and 36,000 USDC. Suppose that the existent amount of LP tokens is 90 and that a liquidity provider adds 1 ETH and 4,000 USDC to the pool. Thus, the pool balances after the deposit are 10 ETH and 40,000 USDC, and the liquidity parameter L satisfies L2 = 400,000. Note that the liquidity provider earns an amount of $$ frac{1}{9}cdot 90=10 LP $$ tokens and that the total amount of existent LP tokens is now 100. Hence, the new liquidity provider owns a share of 10% of the existent LP tokens and thus of the total pool liquidity. Note that the spot price of ETH before and after the deposit is 4,000 USDC/ETH.

Now, suppose that the spot price for 1 ETH goes up to 6,000 USDC. The updated amounts A and B of ETH and USDC can be computed from the following equations:
$$ {displaystyle egin{array}{ll}{A}^{hbox{'}}cdot {B}^{hbox{'}}&amp; ={L}^2=400,000\ {}frac{B^{hbox{'}}}{A^{hbox{'}}}&amp; =6,000end{array}} $$
We obtain that
$$ {A}^{hbox{'}}=sqrt{frac{400,000}{6,000}}approx 8.1649658 $$
and
$$ {B}^{hbox{'}}=sqrt{400,000cdot 6,000}approx 48,989.79. $$
That is, the liquidity pool has now approximately 8.1649658 ETH and 48,989.79 USDC. If the liquidity provider withdraws their position, they will receive 0.81649658 ETH and 4,898.979 USDC since they own a share of 10% of the existent LP tokens. This results in a new portfolio value of approximately
$$ 0.81649658cdot 6,000+4,898.979approx 9,797.96;	extrm{USDC}. $$
However, if they had never decided to be a liquidity provider, their original position would have been worth
$$ 1cdot 6,000+4,000=10,000;	extrm{USDC}. $$

Therefore, they are facing a loss of approximately 202.04 USDC, which amounts to a loss of approximately 2.02% with respect to holding the assets. This loss is called impermanent loss (or divergence loss) and is due to the fact that the amounts of the tokens are moving on a multiplicative inverse curve. It is called impermanent because if the ETH price reverts to the original 4,000 USDC/ETH (which is the spot price at the moment of the deposit), then the pool balances will be again 10 ETH and 40,000 USDC, and in consequence, the liquidity provider’s position will be the same as the one at the beginning and there will be no loss. But if the liquidity provider decides to withdraw their position when the price is 6,000 USDC/ETH, then the previous loss becomes permanent.

We will now generalize what we have seen previously. Consider a Uniswap v2 liquidity pool (with no fees) having two tokens X and Y with balances A0 and B0, respectively. Let M be the amount of existent LP tokens. Suppose that a liquidity provider adds an amount a of token X and an amount b of token Y satisfying that $$ frac{a}{A_0}=frac{b}{B_0} $$. Let $$ q=frac{a}{A_0} $$. We know that the liquidity provider receives an amount qM of LP tokens. In addition, after the deposit, the pool reserves are A = A0 + a for token X and B = B0 + b for token Y, and the total amount of LP tokens is (1 + q)M. Note that the liquidity provider owns a share $$ 
ho =frac{qM}{left(1+q
ight)M}=frac{q}{1+q} $$ of the pool. Since $$ q=frac{a}{A_0}=frac{b}{B_0} $$, we obtain that
$$ 
ho =frac{q}{1+q}=frac{frac{b}{B_0}}{1+frac{b}{B_0}}=frac{b}{B_0+b}=frac{b}{B}. $$
Let $$ p=frac{B}{A} $$ be the spot price after the deposit (which is the same as the spot price before the deposit, as we have previously proved). Hence,
$$ p=frac{B}{A}=frac{B_0}{A_0}=frac{b}{a}. $$
Suppose now that, after some trading activities, the price shifts from p to p. Let A and B be the balances of tokens X and Y, respectively, after theprice change. Hence, $$ {p}^{hbox{'}}=frac{B^{hbox{'}}}{A^{hbox{'}}} $$. And since the pool has no fees, we have that AB = AB. Thus,
$$ {B}^{hbox{'}}={p}^{hbox{'}}{A}^{hbox{'}}={p}^{hbox{'}}frac{AB}{B^{hbox{'}}}. $$
Hence,
$$ {left({B}^{hbox{'}}
ight)}^2={p}^{hbox{'}} AB={p}^{hbox{'}}frac{A}{B}{B}^2=frac{p^{hbox{'}}}{p}{B}^2. $$
Thus,
$$ {B}^{hbox{'}}=sqrt{frac{p^{hbox{'}}}{p}}B. $$
From the analysis at the beginning of this subsection, we know that the value of the liquidity provider’s position in terms of token Y is 2ρB. Note that
$$ 2
ho {B}^{hbox{'}}=2
ho Bsqrt{frac{p^{hbox{'}}}{p}}=2bsqrt{frac{p^{hbox{'}}}{p}}. $$
If the liquidity provider had held the amounts of both tokens instead of depositing them into the pool, the value of their position in terms of token Y would have been
$$ b+{p}^{hbox{'}}a=b+{p}^{hbox{'}}frac{b}{p}=bleft(1+frac{p^{hbox{'}}}{p}
ight). $$
Hence, the impermanent loss in terms of token Y is
$$ 2bsqrt{frac{p^{hbox{'}}}{p}}-bleft(1+frac{p^{hbox{'}}}{p}
ight), $$
and therefore, the fraction of impermanent loss in terms of token Y with respect to the value of the original position is
$$ frac{2bsqrt{frac{p^{hbox{'}}}{p}}-bleft(1+frac{p^{hbox{'}}}{p}
ight)}{bleft(1+frac{p^{hbox{'}}}{p}
ight)}=frac{2bsqrt{frac{p^{hbox{'}}}{p}}}{bleft(1+frac{p^{hbox{'}}}{p}
ight)}-1=frac{2sqrt{frac{p^{hbox{'}}}{p}}}{1+frac{p^{hbox{'}}}{p}}-1. $$
We plot in Figure 2-15 the percentage of impermanent loss as a function of the spot price (expressed as a percentage of the price at the moment of the deposit).
Figure 2-15

Losses of liquidity providers due to price variation compared to holding the funds supplied

Accounting for Fees

The previous analysis did not take fees into consideration. In a pool with a nonzero trading fee, all trading activities add liquidity to the pool in the form of collected fees, which increase the value of the liquidity providers’ positions and hence reduce their impermanent losses.

In order to understand how this works, we will analyze the following example. Consider a liquidity pool with ETH and USDC and with fee ϕ = 0.003. Assume that the pool has 1,000 ETH and 4,000,000 USDC. Note that the spot price is p = 4,000 USDC/ETH. Suppose that a liquidity provider owns a share of 10% of the pool.

Assume that a trader buys 1 ETH in the pool. Applying Equation 2.8, we obtain that the amount of USDC that the trader has to deposit is
$$ b=frac{1cdot 4,000,000}{left(1-0.003
ight)cdot 999}approx 4016. $$
Hence, the updated balances of the pool are
$$ {displaystyle egin{array}{l}{A}^{hbox{'}}=999; ETH,\ {}{B}^{hbox{'}}approx 4,004,016kern0.24em 	extrm{USDC},end{array}} $$

and the new spot price is p ≈ 4,008.024 USDC/ETH.

Observe that the amount of the fee is included in B since the trader was charged the fee on their deposit. Suppose that no other trades are performed and no further liquidity is added. Then, the value of the whole pool is B + p ⋅ A(=2B). And since the liquidity provider owns a share of 10% of the pool, the value of their position is approximately
$$ 800,803.2kern0.24em 	extrm{USDC}. $$
If the liquidity provider had not deposited their tokens into the pool, the value in USDC of their position would have been approximately
$$ 100cdot 4,008.024+400,000=800,802.4 $$
Note that in this case, the impermanent loss is a gain due to the collected fees:
$$ 800,803.2-800,802.4=0.8	extrm{USDC} $$

In the case of a liquidity pool without trading fees (ϕ = 0) and considering the same situation as the previous one, we could perform similar computations to obtain that the liquidity provider would face an impermanent loss of approximately 0.4 USDC.

For liquidity pools with massive trading activity around the starting price, the trading fee becomes more significant for the value of the liquidity provider’s position, and the impermanent loss can sometimes be compensated, resulting in gains for the liquidity providers.

2.3.4 LP Token Swap

In this subsection, we will study the problem of swapping LP tokens of two different liquidity pools. To calculate the fair swap price, we will need to compute the values of those LP tokens.

Consider two liquidity providers, Alice and Bob, who are members of two different liquidity pools P1 and P2, which have liquidity pool tokens LP1 and LP2, respectively. Both Alice and Bob own a certain amount of LP tokens of their respective pools. Let N1 and N2 be the the total minted liquidity tokens in pools P1 and P2, respectively.

We will divide our analysis into two different cases: when the liquidity pools P1 and P2 have at least one common token and when they do not.

Common Token Case

Suppose first that the liquidity pools P1 and P2 have (at least) one common token Y. We want to compute the fair swap price between Alice’s and Bob’s LP tokens. In other words, we want to calculate how the values of the tokens LP1 and LP2 are related.

Let B1 and B2 be the amounts of token Y in pools P1 and P2, respectively. Hence, for j ∈ {1, 2}, the total value of pool Pj (in terms of token Y ) is 2Bj and thus, the value of each LPj token (in terms of token Y ) is
$$ {V}_{LP_j}(Y)=frac{2{B}_j}{N_j} $$
Let P be the the price of one LP1 token in terms of LP2 tokens. That is, one LP1 token is equivalent to P tokens LP2. Hence, one LP1 token and P tokens LP2 have the same value in terms of Y tokens. Thus, $$ {V}_{LP_1}(Y)=Pcdot {V}_{LP_2}(Y) $$, and therefore,
$$ P=frac{V_{LP_1}(Y)}{V_{LP_2}(Y)}=frac{N_2}{N_1}cdot frac{B_1}{B_2}. $$
(2.13)

Summing up, this means that if Alice gives one LP token from pool P1 to Bob, Alice will get $$ frac{N_2}{N_1}cdot frac{B_1}{B_2} $$ tokens from pool P2 in return.

Example 2.6. Suppose Alice and Bob are liquidity providers with the following data:
 

Alice (Pool P1)

Bob (Pool P2)

Total LP tokens

12,000

10,000

LP tokens owned

100

100

Token X

ETH

ETH

Token Y

USDC

USDC

Amount of token X

10,000

9,750

Amount of token Y

40,000,000

39,000,000

Now we apply Equation 2.13 to find the fair swap price between the corresponding LP tokens:
$$ P=frac{10,000}{12,000}cdot frac{40,000,000}{39,000,000}approx 0.8547 $$

Hence, if Alice swaps all her 100 LP tokens from pool P1, she will receive approximately 85.47 LP tokens from pool P2 in return. Note that, although the liquidity of pool P2 is less than that of pool P1, the amount of LP tokens minted in pool P2 is less than that of P1, resulting in a relatively higher price of the token of pool P2 compared with the token of pool P1.

No In-Common Token Case: Oracle Swap

Suppose now that pools P1 and P2 do not have a common token. In that case, we will use external price data to express amounts of certain tokens Y1 ≠ Y2 in terms of amounts of a chosen token Z. We will need the corresponding exchange rates (or prices) to do so.

Let Y1 be a token of pool P1 and let Y2 be a token of pool P2. Suppose that Y1 ≠ Y2. For j ∈ {1, 2}, let Bj be the amount of token Yj in pool Pj. Let Z be a token, and for j ∈ {1, 2}, let pj be the price of token Yj in terms of token Z.

Proceeding as in the previous case, we obtain that for j ∈ {1, 2}, the value of each LPj token in terms of token Yj is
$$ {V}_{LP_j}left({Y}_j
ight)=frac{2{B}_j}{N_j} $$
and hence, the value of each LPj token in terms of token Z is
$$ {V}_{LP_j}(Z)={p}_jcdot frac{2{B}_j}{N_j} $$
Let P be the price of one LP1 token in terms of LP2 tokens. Hence, $$ {V}_{LP_1}(Z)=Pcdot {V}_{LP_2}(Z) $$, and thus,
$$ P=frac{V_{LP_1}(Z)}{V_{LP_2}(Z)}=frac{N_2}{N_1}cdot frac{B_1}{B_2}cdot frac{p_1}{p_2}. $$
(2.14)

Summing up, this means that if Alice gives one LP token from pool P1 to Bob, Alice will get $$ frac{N_2}{N_1}cdot frac{B_1}{B_2}cdot frac{p_1}{p_2} $$ tokens from pool P2 in return.

Example 2.7. Consider two liquidity pools described by the data given in the following table:
 

Pool P1

Pool P2

Total LP tokens

12,000

10,000

Token X

WBTC

XRP

Token Y

ETH

DAI

Amount of token X

100

1,000,000

Amount of token Y

1,200

800,000

We will use USDC as the token Z in which prices will be expressed, with exchange rates of 4,000 USDC/ETH and 1 USDC/DAI.

Applying Equation 2.14, we obtain that the fair swap price between the corresponding LP tokens is
$$ P=frac{10,000}{12,000}cdot frac{1,200}{800,000}cdot frac{4,000}{1}=5 $$

Thus, each LP1 token is worth 5 LP2 tokens. Note that although pool P2 has minted fewer LP tokens, the total value of pool P1 (2 ⋅ 1,200 ⋅ 4,000 = 9,600,000 USDC) is much higher than that of pool P2(2 ⋅ 800,000 ⋅ 1 = 1,600,000 USDC), resulting in a high exchange rate between the corresponding LP tokens.

2.4 Motivating DEX Aggregators

A DEX aggregator is a blockchain-based service that functions as an explorer for the prices and liquidity offered by different Decentralized Exchanges and helps traders find the best price for their trades. In addition, DEX aggregators are equipped with an algorithm that allows users to split the trade they want to do in several trades against different AMMs so as to obtain the best possible price for that trade. Clearly, this is a very enticing service. We will develop a simple example to show why this makes sense and how one can split a trade between two different pools to obtain a better price.

Suppose that we want to trade an amount T of ETH among two different ETH/USDC pools. For example, these can be Uniswap v2 pools on two different networks or one Uniswap and one Sushiswap pool on the same network. We want to find the amount of ETH we should trade in each pool in order to receive the maximum possible amount of USDC.

For j ∈ {1, 2}, let Aj and Bj be the reserves of pool J of ETH and USDC, respectively, and let ϕ1 and ϕ2 be the fees of pools 1 and 2, respectively. Note that if we trade an amount x of ETH in the first pool and the remaining amount Tx of ETH in the second pool, from Equation 2.7, we obtain that the amount of USDC received from the first pool is
$$ frac{B_1left(1-{phi}_1
ight)x}{A_1+left(1-{phi}_1
ight)x} $$
and the amount of USDC received from the second pool is
$$ frac{B_2left(1-{phi}_2
ight)left(T-x
ight)}{A_2+left(1-{phi}_2
ight)left(T-x
ight)} $$
In order to maximize the amount of USDC received when trading the amount T of ETH among the two pools, we have to find the maximum of the function f : [0, T] → ℝ defined by
$$ f(x)=frac{B_1left(1-{phi}_1
ight)x}{A_1+left(1-{phi}_1
ight)x}+frac{B_2left(1-{phi}_2
ight)left(T-x
ight)}{A_2+left(1-{phi}_2
ight)left(T-x
ight)}. $$
For simplicity, let $$ overline{phi_1}=1-{phi}_1 $$ and $$ overline{phi_2}=1-{phi}_2 $$. Note that
$$ f(x)=frac{B_1overline{phi_1}x}{A_1+overline{phi_1}x}+frac{B_2overline{phi_2}left(T-x
ight)}{A_2+overline{phi_2}left(T-x
ight)} $$
$$ ={B}_1cdot frac{overline{phi_1}x+{A}_1-{A}_1}{A_1+overline{phi_1}x}+{B}_2cdot frac{overline{phi_2}left(T-x
ight)+{A}_2-{A}_2}{A_2+overline{phi_2}left(T-x
ight)} $$
$$ ={B}_1left(1-frac{A_1}{A_1+overline{phi_1}x}
ight)+{B}_2left(1-frac{A_2}{A_2+overline{phi_2}left(T-x
ight)}
ight) $$
$$ ={B}_1-frac{A_1{B}_1}{A_1+overline{phi_1}x}+{B}_2-frac{A_2}{A_2+overline{phi_2}left(T-x
ight)}. $$
Thus, the derivative of f is given by
$$ {f}^{hbox{'}}(x)=frac{A_1{B}_1overline{phi_1}}{{left({A}_1+overline{phi_1}x
ight)}^2}-frac{A_2{B}_2overline{phi_2}}{{left({A}_2+overline{phi_2}left(T-x
ight)
ight)}^2} $$
$$ =frac{A_1{B}_1overline{phi_1}{left({A}_2+overline{phi_2}left(T-x
ight)
ight)}^2-{A}_2{B}_2overline{phi_2}{left({A}_1+overline{phi_1}x
ight)}^2}{{left({A}_1+overline{phi_1}x
ight)}^2{left({A}_2+overline{phi_2}left(T-x
ight)
ight)}^2}. $$
Let g(x) denote the numerator of the previous expression, that is,
$$ g(x)={A}_1{B}_1overline{phi_1}{left({A}_2+overline{phi_2}left(T-x
ight)
ight)}^2-{A}_2{B}_2overline{phi_2}{left({A}_1+overline{phi_1}x
ight)}^2. $$

Note that g(x) is just a polynomial of degree 2 and that for any x0 ∈ [0, T], f (x0) = 0 if and only if g(x0) = 0. Moreover, since the denominator of the last expression of f (x) is always positive, we obtain that for any x0 ∈ [0, T], f (x0) > 0 if and only if g(x0) > 0 (and f (x0) < 0 if and only if g(x0) < 0). Thus, in order to study the zeros and the sign of f , we can study the zeros and the sign of g. Recall that since g is a polynomial of degree 2, the zeros of g can be computed from the quadratic formula. Then the positive and negative regions of g can easily be obtained observing the concavity of g from its leading coefficient.

In the following example, we will show how the previous argument can be applied.

Example 2.8. Consider two ETH/USDC liquidity pools with the following reserves:
 

Pool 1

Pool 2

ETH

10,000

10,300

USDC

40,000,000

39,500,000

Φ

0.3%

0.3%

Suppose that we want to trade a total amount of 1,500 ETH between these two pools. To maximize the amount of USDC received, we have to find the maximum value of the function f : [0, 1500] → ℝ defined by
$$ f(x)=frac{40,000,000cdot 0.997cdot x}{10,000+0.997cdot x}+frac{39,500,000cdot 0.997cdot left(1,500-x
ight)}{10,300+0.997cdot left(1,500-x
ight)}. $$
We will thus find the zeros of the derivative f of f and the positive and negative regions of f in order to obtain the intervals in which f is an increasing function and the intervals in which f is a decreasing function. By the argument preceding this example, it is enough to find the zeros and the positive and negative intervals of the function g : ℝ → ℝ defined by
$$ g(x)={A}_1{B}_1overline{phi_1}{left({A}_2+overline{phi_2}left(T-x
ight)
ight)}^2-{A}_2{B}_2overline{phi_2}{left({A}_1+overline{phi_1}x
ight)}^2, $$
in this case,
$$ {displaystyle egin{array}{ll}g(x)=&amp; 10,000cdot 40,000,000cdot 0.997cdot {left(10,300+0.997cdot left(1500-x
ight)
ight)}^2-\ {}&amp; 10,300cdot 39,500,000cdot 0.997cdot {left(10,000+0.997x
ight)}^2.end{array}} $$
We can expand the previous expression to get
$$ {displaystyle egin{array}{ll}g(x)=&amp; -6788534765.05cdot {x}^2-17468117760600000cdot x+\ {}&amp; 14923622515700000000.end{array}} $$
Applying the quadratic formula, we obtain that the roots of g are
$$ {x}_1approx -2574033.44;	extrm{and};{x}_2approx 854.0514. $$

Since the leading coefficient of g is negative, we obtain that g is positive on the interval (x1, x2) and negative on the intervals (−∞, x1) and (x2, +∞) (and we know that the same holds for f ).

And since we are interested in computing the maximum of f on the interval [0, T], from the sign of f , we obtain that f is strictly increasing on the interval [0, x2] and strictly decreasing on the interval [x2, 1,500]. Thus, the function f attains a maximum on x2 ≈ 854.0514. Therefore, in order to maximize the amount of USDC received when trading 1,500 ETH, we have to sell 854.0514 ETH on pool 1 and 1,500 − 854.0514 = 645.9486 ETH on pool 2. By doing so, we will obtain f(854.0514) ≈ 5,463,115.24 USDC.

Observe that if we had performed the whole trade on pool 1, we would have received f(1,500) ≈ 5,203,775.39 USDC, and if we had performed the whole trade on pool 2, we would have received f(0) ≈ 5,008,032.72 USDC.

As we can see from the previous example, we can obtain a much better price if we split the trade between the two pools in a suitable way. However, in order to do so, we need to have all the information about the state of the two pools, and we also need to perform a lot of mathematical computations. DEX aggregators do all this work for us, and hence, they are a valuable and much appreciated tool.

2.5 Summary

In this chapter, we performed a thorough analysis of the Uniswap v2 AMM, introducing the concepts of spot price, effective price, price impact, and impermanent loss. We show how trades are performed in Uniswap v2 and how liquidity can be deposited and withdrawn.

In the next chapter, we will analyze the Balancer AMM, which is a generalization of the Uniswap v2 AMM and allows liquidity pools of more than two assets.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.97.208