Skip to content

Uniswap V3

from uniswappy import *
import numpy as np
import matplotlib.pyplot as plt
import math
user_nm = 'user_intro'
eth_amount = 1000
dai_amount = 1000000
fee = UniV3Utils.FeeAmount.MEDIUM
tick_spacing = UniV3Utils.TICK_SPACINGS[fee]
lwr_tick = UniV3Utils.getMinTick(tick_spacing)
upr_tick = UniV3Utils.getMaxTick(tick_spacing)
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

Eqs (6.13) and (6.15) of Uniswap V3 whitepaper respectively give us:

ΔP=ΔyL,\quad\quad \Delta\sqrt{P} = \frac{\Delta y}{L}, Δ1P=ΔxL\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{\Delta x}{L}

where

ΔP=PnextP,\quad\quad \Delta\sqrt{P} = \sqrt{P_{next}} - \sqrt{P}, Δ1P=1Pnext1P\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{1}{\sqrt{P_{next}}} - \frac{1}{\sqrt{P}}

We calculate received Δx\Delta x to be

Δx=L(1P1Pnext)\quad\quad \Delta x = L (\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{next}}})

where the next price, PnextP_{next}, derived from Δy\Delta y input, with fee included (γ=9971000\gamma = \frac{997}{1000}) gives us:

Pnext=P+997Δy1000L\quad\quad \sqrt{P_{next}} = \sqrt{P} + \frac{997 \Delta y}{1000 L}

dy = 1000
Q96 = 2**96
sqrtp_cur = lp.slot0.sqrtPriceX96/Q96 # convert from Q96 to human
gamma = 997/1000
x = lp.get_reserve(eth)
y = lp.get_reserve(dai)
L = lp.get_liquidity()
sqrtp_next = sqrtp_cur + (gamma*dy) / (L)
dx = L * (1/sqrtp_cur - 1/sqrtp_next)
print(f'We receive {dx:.5f} ETH for {dy} DAI')
We receive 0.99601 ETH for 1000 DAI
out = Swap().apply(lp, dai, user_nm, dy)
lp.summary()
print(f'We receive {out:.5f} ETH for {dy} DAI')
print(f'Confirm price: (1/sqrtp_next^2)={1/sqrtp_next**2:.8f} vs (actual price)={lp.get_price(dai):.8f}')
Exchange ETH-TKN (LP) Real Reserves: ETH = 999.0039930189602, TKN = 1001000.0000000001 Gross Liquidity: 31622.776601683796 We receive 0.99601 ETH for 1000 DAI Confirm price: (1/sqrtp_next^2)=0.00099801 vs (actual price)=0.00099801
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

Eqs (6.13) and (6.15) of Uniswap V3 whitepaper respectively give us:

ΔP=ΔyL,\quad\quad \Delta\sqrt{P} = \frac{\Delta y}{L}, Δ1P=ΔxL\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{\Delta x}{L}

where

ΔP=PnextP,\quad\quad \Delta\sqrt{P} = \sqrt{P_{next}} - \sqrt{P}, Δ1P=1Pnext1P\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{1}{\sqrt{P_{next}}} - \frac{1}{\sqrt{P}}

We calculate received Δy\Delta y to be:

Δy=L(PPnext)\quad\quad \Delta y = L (\sqrt{P} - \sqrt{P_{next}})

where the next price, PnextP_{next}, derived from Δx\Delta x input, with fee included (γ=9971000\gamma = \frac{997}{1000}) give us:

Pnext=11P+997Δx1000L\quad\quad \sqrt{P_{next}} =\frac{1}{ \frac{1}{\sqrt{P}} + \frac{997 \Delta x}{1000 L}}

dx = 1
Q96 = 2**96
sqrtp_cur = lp.slot0.sqrtPriceX96/Q96 # convert from Q96 to human
gamma = 997/1000
x = lp.get_reserve(eth)
y = lp.get_reserve(dai)
L = lp.get_liquidity()
sqrtp_next = 1/(1/sqrtp_cur + (gamma*dx)/(L))
dy = L * (sqrtp_cur - sqrtp_next)
print(f'We receive {dy:.5f} DAI for {dx} ETH')
We receive 996.00698 DAI for 1 ETH
out = Swap().apply(lp, eth, user_nm, dx)
lp.summary()
print(f'We receive {out:.5f} DAI for {dx} ETH')
print(f'Confirm price: (sqrtp_next^2)={sqrtp_next**2:.6f} vs (actual price)={lp.get_price(eth):.6f}')
Exchange ETH-TKN (LP) Real Reserves: ETH = 1001.0000000000001, TKN = 999003.9930189601 Gross Liquidity: 31622.776601683796 We receive 996.00698 DAI for 1 ETH Confirm price: (sqrtp_next^2)=998.008978 vs (actual price)=998.008978
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

Double-sided deltas for both x and y are provided in eqs (6.29) and (6.30) in Uniswap V3 whitepaper, which are as such:

ΔPx=1P1PbΔPy=PPa\quad\quad \Delta P_{x} = \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}} \quad\quad \Delta P_{y} = \sqrt{P} - \sqrt{P_{a}}

Δx=ΔLΔPxΔy=ΔLΔPy\quad\quad \Delta x = \Delta L \Delta P_{x} \quad\quad \Delta y = \Delta L \Delta P_{y}

Once determined, we update our price curve using the CPT withdraw formula, which is given as:

(xΔx)(yΔy)=(LΔL)2\quad\quad (x - \Delta x)(y - \Delta y) = (L - \Delta L)^2

For derivation of above formula, see here

dx = 1
Q96 = 2**96
sqrtp_pa = TickMath.getSqrtRatioAtTick(lwr_tick)/Q96
sqrtp_pb = TickMath.getSqrtRatioAtTick(upr_tick)/Q96
sqrtp_cur = lp.slot0.sqrtPriceX96/Q96
dPx = (1/sqrtp_cur - 1/sqrtp_pb)
dPy = (sqrtp_cur - sqrtp_pa)
dLx = dx/(1/sqrtp_cur - 1/sqrtp_pb)
dx = dLx*dPx
dy = dLx*dPy
new_x = (x-dx)
new_y = (y-dy)
new_L = L-dLx
print(f'The updated reserves are {new_x:8f} ETH and {new_y:8f} DAI, and the updated liquidity is {new_L:8f}')
The updated reserves are 999.000000 ETH and 999000.000000 DAI, and the updated liquidity is 31591.153825
RemoveLiquidity().apply(lp, eth, user_nm, dx, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 999.0000000000001, TKN = 999000.0000000001 Gross Liquidity: 31591.15382508211
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

Double-sided deltas for both x and y are provided in eqs (6.29) and (6.30) in Uniswap V3 whitepaper, which are as such:

ΔPx=1P1PbΔPy=PPa\quad\quad \Delta P_{x} = \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}} \quad\quad \Delta P_{y} = \sqrt{P} - \sqrt{P_{a}}

Δx=ΔLΔPxΔy=ΔLΔPy\quad\quad \Delta x = \Delta L \Delta P_{x} \quad\quad \Delta y = \Delta L \Delta P_{y}

Once determined, we update our price curve using the CPT deposit formula, which is given as:

(x+Δx)(y+Δy)=(L+ΔL)2\quad\quad (x + \Delta x)(y + \Delta y) = (L + \Delta L)^2

For derivation of above formula, see here

dx = 1
Q96 = 2**96
sqrtp_pa = TickMath.getSqrtRatioAtTick(lwr_tick)/Q96
sqrtp_pb = TickMath.getSqrtRatioAtTick(upr_tick)/Q96
sqrtp_cur = lp.slot0.sqrtPriceX96/Q96
dPx = (1/sqrtp_cur - 1/sqrtp_pb)
dPy = (sqrtp_cur - sqrtp_pa)
dLx = dx/(1/sqrtp_cur - 1/sqrtp_pb)
dx = dLx*dPx
dy = dLx*dPy
new_x = x+dx
new_y = y+dy
new_L = L+dLx
print(f'The updated reserves are {new_x:8f} ETH and {new_y:8f} DAI, and the updated liquidity is {new_L:8f}')
The updated reserves are 1001.000000 ETH and 1001000.000000 DAI, and the updated liquidity is 31654.399378
AddLiquidity().apply(lp, eth, user_nm, dx, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1001.0000000000001, TKN = 1001000.0000000001 Gross Liquidity: 31654.39937828548
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

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

Δx=ΔL(1P1Pb)\quad\quad \Delta x = \Delta L ( \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}}), Δy=ΔL(PPa)\quad\quad \Delta y = \Delta L (\sqrt{P} - \sqrt{P_{a}})

Δyws=Δy+(LΔL)(P11P+997Δx1000(LΔL))\quad\quad \Delta y_{ws} = \Delta y + (L - \Delta L) (\sqrt{P} - \frac{1}{ \frac{1}{\sqrt{P}} + \frac{997 \Delta x}{1000(L - \Delta L)} })

To solve for ΔL\Delta L we plug Δx\Delta x and Δy\Delta y into Δyws\Delta y_{ws}, after some algebra, we get the following quadratic:

(997ΔPyΔPxP1000ΔPxP997ΔPy)ΔL2(997\Delta P_{y} \Delta P_{x} \sqrt{P}- 1000 \Delta P_{x} P - 997 \Delta P_{y})\Delta L^2

+(997ΔPyPΔyw+1000ΔywP+997LΔPy+1000LΔPxP)ΔL\quad\quad + (-997 \Delta P_{y} \sqrt{P} \Delta y_{w} + 1000 \Delta y_{w}P + 997 L \Delta P_{y} + 1000 L \Delta P_{x} P)\Delta L

(1000LΔywsP)=0\quad\quad - (1000 L \Delta y_{ws} P) = 0

Given the fact we know our desired withdraw amount, Δyws\Delta y_{ws}, we solve the above quadratic to determine our single-sided settlement amount in terms of ΔL\Delta L

Perform single-sided withdrawal using uniswappy

Section titled “Perform single-sided withdrawal using uniswappy”
WithdrawSwap().apply(lp, eth, user_nm, 1, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 999.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31606.937511796757
eth = ERC20("ETH", "0x09")
dai = ERC20("TKN", "0x111")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP",
address="0x011", version = 'V3',
tick_spacing = tick_spacing,
fee = fee)
factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
Join().apply(lp, user_nm, eth_amount, dai_amount, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 1000000.0000000001 Gross Liquidity: 31622.776601683796

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

Δx=ΔL(1P1Pb)\quad\quad \Delta x = \Delta L ( \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}}), Δy=ΔL(PPa)\quad\quad \Delta y = \Delta L (\sqrt{P} - \sqrt{P_{a}})

Δxws=Δx+(LΔL)(1P1P+997Δy1000(LΔL))\quad\quad \Delta x_{ws} = \Delta x + (L - \Delta L) (\frac{1}{\sqrt{P}} - \frac{1}{ \sqrt{P} + \frac{997 \Delta y}{1000(L - \Delta L)} })

To solve for ΔL\Delta L we plug Δx\Delta x and Δy\Delta y into Δxws\Delta x_{ws}, after some algebra, we get the following quadratic:

(997ΔPyΔPxP1000ΔPxP997ΔPy)ΔL2(997\Delta P_{y} \Delta P_{x} \sqrt{P}- 1000 \Delta P_{x} P - 997 \Delta P_{y})\Delta L^2

+(997ΔPyPΔxws+1000ΔxwsP+997LΔPy+1000LΔPxP)ΔL\quad\quad + (-997 \Delta P_{y} \sqrt{P} \Delta x_{ws} + 1000 \Delta x_{ws}P + 997L \Delta P_{y} + 1000 L \Delta P_{x} P)\Delta L

(1000LΔxwsP)=0\quad\quad - (1000 L \Delta x_{ws} P) = 0

Given the fact we know our desired withdraw amount, Δxws\Delta x_{ws}, we solve the above quadratic to determine our single-sided settlement amount in terms of ΔL\Delta L

Perform single-sided withdrawal using uniswappy

Section titled “Perform single-sided withdrawal using uniswappy”
WithdrawSwap().apply(lp, dai, user_nm, 1000, lwr_tick, upr_tick)
lp.summary()
Exchange ETH-TKN (LP) Real Reserves: ETH = 1000.0000000000001, TKN = 999000.0 Gross Liquidity: 31606.937511796754