Uniswap V3

[24]:
from uniswappy import *
import numpy as np
import matplotlib.pyplot as plt
import math
[25]:
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)

(1a) Swap: dy -> dx

[26]:
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:

\(\quad\quad \Delta\sqrt{P} = \frac{\Delta y}{L},\) \(\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{\Delta x}{L}\)

where

\(\quad\quad \Delta\sqrt{P} = \sqrt{P_{next}} - \sqrt{P},\) \(\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{1}{\sqrt{P_{next}}} - \frac{1}{\sqrt{P}}\)

We calculate received \(\Delta x\) to be

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

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

\(\quad\quad \sqrt{P_{next}} = \sqrt{P} + \frac{997 \Delta y}{1000 L}\)

Perform dy -> dx swap using dervation

[27]:
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

Perform dy -> dx swap using uniswappy

[28]:
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

(1b) Swap: dx -> dy

[29]:
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:

\(\quad\quad \Delta\sqrt{P} = \frac{\Delta y}{L},\) \(\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{\Delta x}{L}\)

where

\(\quad\quad \Delta\sqrt{P} = \sqrt{P_{next}} - \sqrt{P},\) \(\quad\quad \Delta\frac{1}{\sqrt{P}} = \frac{1}{\sqrt{P_{next}}} - \frac{1}{\sqrt{P}}\)

We calculate received \(\Delta y\) to be:

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

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

\(\quad\quad \sqrt{P_{next}} =\frac{1}{ \frac{1}{\sqrt{P}} + \frac{997 \Delta x}{1000 L}}\)

Perform dx -> dy swap using dervation

[30]:
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
[31]:
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

(2) Double-sided withdrawal

[32]:
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:

\(\quad\quad \Delta P_{x} = \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}} \quad\quad \Delta P_{y} = \sqrt{P} - \sqrt{P_{a}}\)

\(\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:

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

For derivation of above formula, see here

Calculate withdrawal using derivation

[33]:
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

Perform withdrawal using uniswappy

[34]:
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

(3) Double-sided deposit

[35]:
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:

\(\quad\quad \Delta P_{x} = \frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{b}}} \quad\quad \Delta P_{y} = \sqrt{P} - \sqrt{P_{a}}\)

\(\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:

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

For derivation of above formula, see here

Calculate deposit using derivation

[36]:
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

Perform deposit using uniswappy

[37]:
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

(4a) Single-sided withdrawal: dx

[38]:
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

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

\(\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 \(\Delta L\) we plug \(\Delta x\) and \(\Delta y\) into \(\Delta y_{ws}\), after some algebra, we get the following quadratic:

\((997\Delta P_{y} \Delta P_{x} \sqrt{P}- 1000 \Delta P_{x} P - 997 \Delta P_{y})\Delta L^2\)

\(\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\)

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

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

Perform single-sided withdrawal using uniswappy

[39]:
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

(4b) Single-sided withdrawal: dy

[40]:
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

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

\(\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 \(\Delta L\) we plug \(\Delta x\) and \(\Delta y\) into \(\Delta x_{ws}\), after some algebra, we get the following quadratic:

\((997\Delta P_{y} \Delta P_{x} \sqrt{P}- 1000 \Delta P_{x} P - 997 \Delta P_{y})\Delta L^2\)

\(\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\)

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

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

Perform single-sided withdrawal using uniswappy

[41]:
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