Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update implementations and deployment addresses #34

Merged
merged 51 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d00b84a
introduce A_oracle in implementation
bout3fiddy Nov 6, 2023
ee495c2
set A_oracle only once
bout3fiddy Nov 8, 2023
19ff82a
set lower bouns for fetched A
bout3fiddy Nov 8, 2023
5d643a6
fix comment
bout3fiddy Nov 8, 2023
bdab80a
use min max to cap instead of asserts
bout3fiddy Nov 8, 2023
eeb3773
add fraxsDAI deployment script
bout3fiddy Nov 10, 2023
aeca06b
add default return value for approve
bout3fiddy Nov 10, 2023
7fb7a1b
fix metapool impl for mainnet only
bout3fiddy Nov 11, 2023
304d17b
deploy usdc3crv
bout3fiddy Nov 11, 2023
e681684
add cap to spot prices going into oracle
bout3fiddy Nov 14, 2023
edc90fc
Merge branch 'main' into feat/A_oracle
bout3fiddy Nov 14, 2023
9228406
deploy new ng implementation
bout3fiddy Nov 14, 2023
71abaaf
Merge branch 'feat/A_oracle' of https://github.com/curvefi/stableswap…
bout3fiddy Nov 14, 2023
06c712e
redeploy metapool implementation
bout3fiddy Nov 15, 2023
0d14719
fix: metapool impl should use static arrays for zap compatibility
bout3fiddy Nov 16, 2023
f720b1a
precise tests
bout3fiddy Nov 16, 2023
02162cb
new metapool impl and pools
bout3fiddy Nov 16, 2023
5f582a6
deployments
bout3fiddy Nov 16, 2023
cf74a88
fix: asset_type specific _transfer_out logic
bout3fiddy Nov 27, 2023
4bb402e
fix: use correct input for oracle update in remove_liquidity_imbalance
bout3fiddy Nov 27, 2023
db58dd6
reduce unnecessary storage write if need be
bout3fiddy Nov 29, 2023
4511734
set oracles (now called rate_oracles to differentiate between price a…
bout3fiddy Nov 29, 2023
ba4343c
match unsafe math in non meta impl
bout3fiddy Nov 29, 2023
f6622ff
log event when ma exp time is changed
bout3fiddy Nov 29, 2023
6de8afd
improve for loop in non meta impl to use new bounds
bout3fiddy Nov 29, 2023
12f1b70
fix incorrect division
bout3fiddy Nov 29, 2023
8abe95e
fix unsafe div #2
bout3fiddy Nov 29, 2023
7483832
fix tests
bout3fiddy Nov 29, 2023
bb8e9db
fix unsafe div
bout3fiddy Nov 29, 2023
1917d81
change storage before external call; add reentrancy lock for withdraw…
bout3fiddy Dec 6, 2023
e2efdc9
remove extra Transfer log in remove liquidity one
bout3fiddy Dec 6, 2023
8c97199
use more correct input in TokenExchange
bout3fiddy Dec 6, 2023
fc0dc6e
check approve in meta constructor
bout3fiddy Dec 6, 2023
e42c3ae
emit event in transferFrom
bout3fiddy Dec 6, 2023
33c539b
restrict non-NG base pools in metapools
bout3fiddy Dec 6, 2023
7ac164b
edge cases in D calc; remove deployed addresses which need to be repl…
bout3fiddy Dec 7, 2023
72e34f6
emit correct value in event
bout3fiddy Dec 7, 2023
d2f21f8
do nothing if fee receiver is empty
bout3fiddy Dec 7, 2023
cdbf9ed
some math updates and some gas improvements in meta
bout3fiddy Dec 7, 2023
dcd4471
remove unnecessary storage read when not necessary
bout3fiddy Dec 7, 2023
2786498
check _min_amounts length
bout3fiddy Dec 7, 2023
75c7c93
do not send tokens to zero address
bout3fiddy Dec 7, 2023
1e8dc1a
fix typos
bout3fiddy Dec 7, 2023
9bace0d
add some natspec
bout3fiddy Dec 7, 2023
4b3cb6d
allow ng pools as base pools
bout3fiddy Dec 8, 2023
06b398f
fix marker fixtures to autouse; set filterwarning in pytest config
bout3fiddy Dec 8, 2023
1fb6428
remove A_oracle and focus on that on a different PR
bout3fiddy Dec 8, 2023
c039dc5
use immutable boolean for rebasing checks
bout3fiddy Dec 12, 2023
7e0f67e
add note on asset type usage
bout3fiddy Dec 12, 2023
8eaf5e2
rename var for clarity and add some more codecomments
bout3fiddy Dec 12, 2023
b3fc89a
deployments
bout3fiddy Dec 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
411 changes: 229 additions & 182 deletions contracts/main/CurveStableSwapMetaNG.vy

Large diffs are not rendered by default.

294 changes: 158 additions & 136 deletions contracts/main/CurveStableSwapNG.vy

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions contracts/main/CurveStableSwapNGMath.vy
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,14 @@ def get_D(

D: uint256 = S
Ann: uint256 = _amp * _n_coins
D_P: uint256 = 0
Dprev: uint256 = 0

for i in range(255):

D_P = D
D_P: uint256 = D
for x in _xp:
D_P = D_P * D / (x * _n_coins) # If division by 0, this will be borked: only withdrawal will work. And that is good
Dprev = D
D_P = D_P * D / x # If division by 0, this will be borked: only withdrawal will work. And that is good
D_P /= pow_mod256(_n_coins, _n_coins)
Dprev: uint256 = D

# (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P)
D = (
Expand Down Expand Up @@ -219,7 +218,7 @@ def exp(x: int256) -> uint256:

# If the result is `< 0.5`, we return zero. This happens when we have the following:
# "x <= floor(log(0.5e18) * 1e18) ~ -42e18".
if (x <= -42139678854452767551):
if (x <= -41446531673892822313):
return empty(uint256)

# When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer.
Expand Down
17 changes: 11 additions & 6 deletions contracts/main/CurveStableSwapNGViews.vy
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,13 @@ def _base_calc_token_amount(

else:

raise "base_n_coins > 3 not supported yet."
base_inputs: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS])
for i in range(base_n_coins, bound=MAX_COINS):
if i == convert(base_i, uint256):
base_inputs.append(dx)
else:
base_inputs.append(0)
return StableSwapNG(base_pool).calc_token_amount(base_inputs, is_deposit)


@internal
Expand Down Expand Up @@ -575,15 +581,14 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256, N_COINS: uint256) ->

D: uint256 = S
Ann: uint256 = _amp * N_COINS
D_P: uint256 = 0
Dprev: uint256 = 0

for i in range(255):

D_P = D
D_P: uint256 = D
for x in _xp:
D_P = D_P * D / (x * N_COINS)
Dprev = D
D_P = D_P * D / x
D_P /= pow_mod256(N_COINS, N_COINS)
Dprev: uint256 = D

D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P)
# Equality with the precision of 1
Expand Down
2 changes: 1 addition & 1 deletion contracts/main/LiquidityGauge.vy
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ def permit(
assert ecrecover(digest, _v, _r, _s) == _owner # dev: invalid signature

self.allowance[_owner][_spender] = _value
self.nonces[_owner] = nonce + 1
self.nonces[_owner] = unsafe_add(nonce, 1)

log Approval(_owner, _spender, _value)
return True
Expand Down
314 changes: 314 additions & 0 deletions contracts/mocks/Zap.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
# @version 0.3.10
"""
@title "Zap" Depositer for permissionless USD metapools
@author Curve.Fi
@license Copyright (c) Curve.Fi, 2021 - all rights reserved
"""

interface ERC20:
def transfer(_receiver: address, _amount: uint256): nonpayable
def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable
def approve(_spender: address, _amount: uint256): nonpayable
def decimals() -> uint256: view
def balanceOf(_owner: address) -> uint256: view

interface CurveMeta:
def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256, _receiver: address) -> uint256: nonpayable
def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: nonpayable
def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256, _receiver: address) -> uint256: nonpayable
def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: nonpayable
def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view
def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: view
def coins(i: uint256) -> address: view

interface CurveBase:
def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable
def remove_liquidity(_amount: uint256, min_amounts: uint256[BASE_N_COINS]): nonpayable
def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable
def remove_liquidity_imbalance(amounts: uint256[BASE_N_COINS], max_burn_amount: uint256): nonpayable
def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view
def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view
def coins(i: uint256) -> address: view
def fee() -> uint256: view


N_COINS: constant(uint256) = 2
MAX_COIN: constant(uint256) = N_COINS-1
BASE_N_COINS: constant(uint256) = 3
N_ALL_COINS: constant(uint256) = N_COINS + BASE_N_COINS - 1

N_COINS_128: constant(int128) = 2
MAX_COIN_128: constant(int128) = N_COINS-1
BASE_N_COINS_128: constant(int128) = 3
N_ALL_COINS_128: constant(int128) = N_COINS + BASE_N_COINS - 1

FEE_DENOMINATOR: constant(uint256) = 10 ** 10
FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee

BASE_POOL: immutable(address)
BASE_LP_TOKEN: immutable(address)
BASE_COINS: immutable(address[3])

# coin -> pool -> is approved to transfer?
is_approved: HashMap[address, HashMap[address, bool]]


@external
def __init__(_base_pool: address, _base_lp_token: address, _base_coins: address[3]):
"""
@notice Contract constructor
"""

BASE_POOL = _base_pool
BASE_LP_TOKEN = _base_lp_token
BASE_COINS = _base_coins

base_coins: address[3] = BASE_COINS
for coin in base_coins:
ERC20(coin).approve(BASE_POOL, MAX_UINT256)


@external
def add_liquidity(
_pool: address,
_deposit_amounts: uint256[N_ALL_COINS],
_min_mint_amount: uint256,
_receiver: address = msg.sender,
) -> uint256:
"""
@notice Wrap underlying coins and deposit them into `_pool`
@param _pool Address of the pool to deposit into
@param _deposit_amounts List of amounts of underlying coins to deposit
@param _min_mint_amount Minimum amount of LP tokens to mint from the deposit
@param _receiver Address that receives the LP tokens
@return Amount of LP tokens received by depositing
"""
meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS])
base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
deposit_base: bool = False
base_coins: address[3] = BASE_COINS

if _deposit_amounts[0] != 0:
coin: address = CurveMeta(_pool).coins(0)
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True
ERC20(coin).transferFrom(msg.sender, self, _deposit_amounts[0])
meta_amounts[0] = _deposit_amounts[0]

for i in range(1, N_ALL_COINS):
amount: uint256 = _deposit_amounts[i]
if amount == 0:
continue
deposit_base = True
base_idx: uint256 = i - 1
coin: address = base_coins[base_idx]

ERC20(coin).transferFrom(msg.sender, self, amount)
# Handle potential Tether fees
if i == N_ALL_COINS - 1:
base_amounts[base_idx] = ERC20(coin).balanceOf(self)
else:
base_amounts[base_idx] = amount

# Deposit to the base pool
if deposit_base:
coin: address = BASE_LP_TOKEN
CurveBase(BASE_POOL).add_liquidity(base_amounts, 0)
meta_amounts[MAX_COIN] = ERC20(coin).balanceOf(self)
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True

# Deposit to the meta pool
return CurveMeta(_pool).add_liquidity(meta_amounts, _min_mint_amount, _receiver)


@external
def remove_liquidity(
_pool: address,
_burn_amount: uint256,
_min_amounts: uint256[N_ALL_COINS],
_receiver: address = msg.sender
) -> uint256[N_ALL_COINS]:
"""
@notice Withdraw and unwrap coins from the pool
@dev Withdrawal amounts are based on current deposit ratios
@param _pool Address of the pool to deposit into
@param _burn_amount Quantity of LP tokens to burn in the withdrawal
@param _min_amounts Minimum amounts of underlying coins to receive
@param _receiver Address that receives the LP tokens
@return List of amounts of underlying coins that were withdrawn
"""
ERC20(_pool).transferFrom(msg.sender, self, _burn_amount)

min_amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
amounts: uint256[N_ALL_COINS] = empty(uint256[N_ALL_COINS])

# Withdraw from meta
meta_received: uint256[N_COINS] = CurveMeta(_pool).remove_liquidity(
_burn_amount,
[_min_amounts[0], convert(0, uint256)]
)

# Withdraw from base
for i in range(BASE_N_COINS):
min_amounts_base[i] = _min_amounts[MAX_COIN+i]
CurveBase(BASE_POOL).remove_liquidity(meta_received[1], min_amounts_base)

# Transfer all coins out
coin: address = CurveMeta(_pool).coins(0)
ERC20(coin).transfer(_receiver, meta_received[0])
amounts[0] = meta_received[0]

base_coins: address[BASE_N_COINS] = BASE_COINS
for i in range(1, N_ALL_COINS):
coin = base_coins[i-1]
amounts[i] = ERC20(coin).balanceOf(self)
ERC20(coin).transfer(_receiver, amounts[i])

return amounts


@external
def remove_liquidity_one_coin(
_pool: address,
_burn_amount: uint256,
i: int128,
_min_amount: uint256,
_receiver: address=msg.sender
) -> uint256:
"""
@notice Withdraw and unwrap a single coin from the pool
@param _pool Address of the pool to deposit into
@param _burn_amount Amount of LP tokens to burn in the withdrawal
@param i Index value of the coin to withdraw
@param _min_amount Minimum amount of underlying coin to receive
@param _receiver Address that receives the LP tokens
@return Amount of underlying coin received
"""
ERC20(_pool).transferFrom(msg.sender, self, _burn_amount)

coin_amount: uint256 = 0
if i == 0:
coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, i, _min_amount, _receiver)
else:
base_coins: address[BASE_N_COINS] = BASE_COINS
coin: address = base_coins[i - MAX_COIN_128]
# Withdraw a base pool coin
coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, MAX_COIN_128, 0, self)
CurveBase(BASE_POOL).remove_liquidity_one_coin(coin_amount, i-MAX_COIN_128, _min_amount)
coin_amount = ERC20(coin).balanceOf(self)
ERC20(coin).transfer(_receiver, coin_amount)

return coin_amount


@external
def remove_liquidity_imbalance(
_pool: address,
_amounts: uint256[N_ALL_COINS],
_max_burn_amount: uint256,
_receiver: address=msg.sender
) -> uint256:
"""
@notice Withdraw coins from the pool in an imbalanced amount
@param _pool Address of the pool to deposit into
@param _amounts List of amounts of underlying coins to withdraw
@param _max_burn_amount Maximum amount of LP token to burn in the withdrawal
@param _receiver Address that receives the LP tokens
@return Actual amount of the LP token burned in the withdrawal
"""
fee: uint256 = CurveBase(BASE_POOL).fee() * BASE_N_COINS / (4 * (BASE_N_COINS - 1))
fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision

# Transfer the LP token in
ERC20(_pool).transferFrom(msg.sender, self, _max_burn_amount)

withdraw_base: bool = False
amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])
amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS])

# determine amounts to withdraw from base pool
for i in range(BASE_N_COINS):
amount: uint256 = _amounts[MAX_COIN + i]
if amount != 0:
amounts_base[i] = amount
withdraw_base = True

# determine amounts to withdraw from metapool
amounts_meta[0] = _amounts[0]
if withdraw_base:
amounts_meta[MAX_COIN] = CurveBase(BASE_POOL).calc_token_amount(amounts_base, False)
amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1

# withdraw from metapool and return the remaining LP tokens
burn_amount: uint256 = CurveMeta(_pool).remove_liquidity_imbalance(amounts_meta, _max_burn_amount)
ERC20(_pool).transfer(msg.sender, _max_burn_amount - burn_amount)

# withdraw from base pool
if withdraw_base:
CurveBase(BASE_POOL).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN])
coin: address = BASE_LP_TOKEN
leftover: uint256 = ERC20(coin).balanceOf(self)

if leftover > 0:
# if some base pool LP tokens remain, re-deposit them for the caller
if not self.is_approved[coin][_pool]:
ERC20(coin).approve(_pool, MAX_UINT256)
self.is_approved[coin][_pool] = True
burn_amount -= CurveMeta(_pool).add_liquidity([convert(0, uint256), leftover], 0, msg.sender)

# transfer withdrawn base pool tokens to caller
base_coins: address[BASE_N_COINS] = BASE_COINS
for i in range(BASE_N_COINS):
ERC20(base_coins[i]).transfer(_receiver, amounts_base[i])

# transfer withdrawn metapool tokens to caller
if _amounts[0] > 0:
coin: address = CurveMeta(_pool).coins(0)
ERC20(coin).transfer(_receiver, _amounts[0])

return burn_amount


@view
@external
def calc_withdraw_one_coin(_pool: address, _token_amount: uint256, i: int128) -> uint256:
"""
@notice Calculate the amount received when withdrawing and unwrapping a single coin
@param _pool Address of the pool to deposit into
@param _token_amount Amount of LP tokens to burn in the withdrawal
@param i Index value of the underlying coin to withdraw
@return Amount of coin received
"""
if i < MAX_COIN_128:
return CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, i)
else:
_base_tokens: uint256 = CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, MAX_COIN_128)
return CurveBase(BASE_POOL).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN_128)


@view
@external
def calc_token_amount(_pool: address, _amounts: uint256[N_ALL_COINS], _is_deposit: bool) -> uint256:
"""
@notice Calculate addition or reduction in token supply from a deposit or withdrawal
@dev This calculation accounts for slippage, but not fees.
Needed to prevent front-running, not for precise calculations!
@param _pool Address of the pool to deposit into
@param _amounts Amount of each underlying coin being deposited
@param _is_deposit set True for deposits, False for withdrawals
@return Expected amount of LP tokens received
"""
meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS])
base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS])

meta_amounts[0] = _amounts[0]
for i in range(BASE_N_COINS):
base_amounts[i] = _amounts[i + MAX_COIN]

base_tokens: uint256 = CurveBase(BASE_POOL).calc_token_amount(base_amounts, _is_deposit)
meta_amounts[MAX_COIN] = base_tokens

return CurveMeta(_pool).calc_token_amount(meta_amounts, _is_deposit)
Loading