Coinmonks

Coinmonks is a non-profit Crypto Educational Publication. Other Project — https://coincodecap.com/ & Email — gaurav@coincodecap.com

Uniswap V2 Math Tutorial using UniswapPy

8 min readMar 5, 2025

--

  • Uniswap V2 math walk-through tutorial using Python
  • We will be covering: swaps, double-sided withdraw and deposits, single-sided withdraw and deposits
  • Utilizes the DeFiPy python suite

1. Introduction

In 2016, Vitalik Buterin (ie, the founder of Ethereum) submitted a Reddit post where he proposed his raw idea of the automated market maker (AMM). A year later, Hayden Adams began working on turning this idea into a functional product and founded Uniswap, which was launched on the Ethereum mainnet in November 2018.

An automated market maker (AMM) protocol is the mechanism used by decentralized exchanges (DEXs). These DEXs consist of liquidity pools (LPs) represented by various trading pairs (eg. ETH/USDC, ETH/WBTC, etc.) acting as the AMM. Trading activity within these LPs are governed via smart contract through a simple and elegant concept called the constant product trading (CPT) formula denoted as:

In this article, we walk through the basic underpinning mathematical concepts behind the Uniswap V2 AMM protocol while using the UniswapPy python package to guide the discussion. This is useful for anyone who is unfamiliar with Uniswap V2 and is looking to onboard themselves into AMMs for the first time.

2. Swap derivation

A swap is when someone comes to the LP with an amount of one of the pair assets, say Δy, and expects to receive an amount of the opposing asset, Δx. Since we are not providing any new liquidity to the pool, the liquidity, L, should remain unchanged post exchange. This operation results in a new position on the CPT curve, as per image to the intro of this article. To start, let’s setup our mock LP:

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1000.0, DAI = 1000000.0
Liquidity: 31622.776601683792

Given the CPT formula:

with 0.3% swap fee:

Since L is unchanged when performing a swap, the CPT swap formula with fee is given as:

where Δy is the amount swapped into the LP and Δx is the receive amount. To determine this receive amount, we isolate Δx:

After performing some additional algebra, we get the swap derivation:

Now, we can manually calculate:

dy = 1000
gamma = 997/1000
x = lp.get_reserve(eth)
y = lp.get_reserve(dai)

dx = (gamma*x*dy)/(y + gamma*dy)

print(f'We receive {dx:.5f} ETH for {dy} DAI')

We receive 0.99601 ETH for 1000 DAI

Next, we perform a swap using UniswapPy as confirmation:

out = Swap().apply(lp, dai, user_nm, dy)
lp.summary()

print(f'We receive {out:.5f} ETH for {dy} DAI')

Exchange ETH-DAI (LP)
Reserves: ETH = 999.00399301896, DAI = 1001000.0
Liquidity: 31622.776601683792

We receive 0.99601 ETH for 1000 DAI

3. Doubled-sided withdraw derivation

When a liquidity provider wishes to withdraw their funds from an LP, they must withdraw both x and y simultaneously. Doing this will reduce the amount of liquidity within the pool by ΔL and will push the CPT curve toward the origin. This will result in no change in the price post withdrawal. Let’s again setup our mock LP:

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1000.0, DAI = 1000000.0
Liquidity: 31622.776601683792

Given CPT formula:

Price is determined as such:

Given P above, calculate Δx and Δy while maintaining price integrity:

To maintain price integrity, we take the LHS of CPT formula and deduct Δx and Δy from both x and y :

CPT withdraw formula:

Now, we can manually calculate a withdraw using this derivation:

dL = dx*L/x
dy = y*dL/L
new_x = (x-dx)
new_y = (y-dy)
new_L = L-dL

print(f'The updated reserves are {new_x} ETH and {new_y} DAI, and the updated liquidity is {new_L:8f}')

The updated reserves are 999.0 ETH and 999000.0 DAI, and the updated liquidity is 31591.153825

Next, we perform a withdraw using UniswapPy as confirmation:

RemoveLiquidity().apply(lp, eth, user_nm, dx)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 999.0, DAI = 999000.0
Liquidity: 31591.15382508211

4. Doubled-sided deposit derivation

When a liquidity provider wishes to deposit their funds into an LP, they must deposit both x and y simultaneously. Doing this will increase the amount of liquidity within the pool by ΔL and will push the CPT curve out from the origin. This will result in no change in the price post deposit. Let’s again setup our mock LP:

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1000.0, DAI = 1000000.0
Liquidity: 31622.776601683792

We know from above, to maintain price integrity, double-sided change amounts Δx and Δy are determined as such:

Take LHS of CPT formula and add Δx, Δy to both x and y to maintain price integrity:

Next, we perform algebra similar to above and get the CPT deposit formula:

Now, we can manually calculate a deposit using this derivation:

dL = dx*L/x
dy = y*dL/L
new_x = (x+dx)
new_y = (y+dy)
new_L = L+dL

print(f'The updated reserves are {new_x} ETH and {new_y} DAI, and the updated liquidity is {new_L:8f}')

The updated reserves are 1001.0 ETH and 1001000.0 DAI, and the updated liquidity is 31654.399378

Next, we perform a deposit using UniswapPy as confirmation:

AddLiquidity().apply(lp, eth, user_nm, dx)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1001.0, DAI = 1001000.0
Liquidity: 31654.399378285478

5. Single-sided withdraw derivation

Instead of withdrawing both x and y simultaneously, here we derive the math on how to withdraw just one of the assets (x or y); this is what we call a single-sided withdraw. This is done by performing a withdraw followed by a swap on one of the undesired assets in one autonomous procedure. This operation is not found in the original Uniswap V2 pairing code, and is an innovation specific to the UniswapPy python package known as a WithdrawSwap. Let’s again setup our mock LP:

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1000.0, DAI = 1000000.0
Liquidity: 31622.776601683792

Given Δx, Δy for withdraw:

Also, given swap equation:

A single-sided withdraw constitutes of the sum total of a withdraw and a swap, otherwise known as a WithdrawSwap, and is given by:

Plug Δx, Δy into Δyₛ and after performing some algebra we get:

Given the fact we know our desired withdraw amount, Δyₛ, we solve the above quadratic to determine our single-sided settlement amount in terms of ΔL; for more detailed information, see my previous medium article. Now we can perform a single-sided withdraw using UniswapPy:

dy = 1
amount_out = WithdrawSwap().apply(lp, eth, user_nm, dy)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 999.0, DAI = 1000000.0
Liquidity: 31606.937511796754

6. Single-sided deposit derivation

Instead of depositing both x and y simultaneously, here we derive the math on how to deposit just one of the assets (x or y); this is what we call a single-sided deposit. This is down by performing a swap followed by a deposit in one autonomous procedure. This operation is not found in the original Uniswap V2 pairing code, and is an innovation specific to the UniswapPy python package known as a SwapDeposit. Let’s again setup our mock LP:

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1000.0, DAI = 1000000.0
Liquidity: 31622.776601683792

A single-sided deposit constitutes of the sum total of a swap followed by a deposit, otherwise known as a SwapDeposit, and is given by:

Given swap equation:

Thus, we get this system of equations:

where γ = 997/1000. To solve for ratio α, we plug Δyₛ into Δxₛ, and after some algebra, we get this quadratic:

For more detailed information on this, see my previous medium article. Now we can perform a single-sided deposit using UniswapPy:

dy = 1
dep = SwapDeposit().apply(lp, eth, user_nm, dy)
lp.summary()

Exchange ETH-DAI (LP)
Reserves: ETH = 1001.0, DAI = 1000000.0
Liquidity: 31638.56029234534

7. Summary

In this article we cover the fundamental math concepts behind the Uniswap V2 protocol using the DeFiPy python suite, the code behind this presentation is readily available on the DeFiPy Github repos. The math behind single-sided withdraws and deposits are also introduced, which have not been previously discussed in the Uniswap community nor have they been covered in the research literature (to my knowledge). The utility of these autonomous operations are very useful as only one of the pair assets are required to make the deposit or withdrawal.

Please keep and eye out for our next article, where we will be covering the math behind Uniswap V3.

--

--

Coinmonks
Coinmonks

Published in Coinmonks

Coinmonks is a non-profit Crypto Educational Publication. Other Project — https://coincodecap.com/ & Email — gaurav@coincodecap.com

No responses yet