From 11c152d388662917c0ed810f31783ce86c850896 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:19:27 +0200 Subject: [PATCH] use more precise calculations for legacy basepools --- contracts/main/CurveStableSwapNGViews.vy | 86 +++++++++++++++--------- poetry.lock | 30 ++------- pyproject.toml | 2 +- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 2f352b5c..cde392bc 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -8,6 +8,8 @@ integrators """ +from vyper.interfaces import ERC20Detailed + interface StableSwapNG: def N_COINS() -> uint256: view def BASE_POOL() -> address: view @@ -21,6 +23,7 @@ interface StableSwapNG: def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def totalSupply() -> uint256: view def offpeg_fee_multiplier() -> uint256: view + def coins(i: uint256) -> address: view A_PRECISION: constant(uint256) = 100 MAX_COINS: constant(uint256) = 8 @@ -28,7 +31,9 @@ PRECISION: constant(uint256) = 10 ** 18 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -VERSION: public(constant(String[8])) = "1.1.0" +VERSION: public(constant(String[8])) = "1.2.0" +# first version was: 0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA (ethereum mainnet) +# second version was: 0x13526206545e2DC7CcfBaF28dC88F440ce7AD3e0 (ethereum mainnet) # ------------------------------ Public Getters ------------------------------ @@ -148,6 +153,7 @@ def get_dy_underlying( N_COINS: uint256 = StableSwapNG(pool).N_COINS() MAX_COIN: int128 = convert(N_COINS, int128) - 1 BASE_POOL: address = StableSwapNG(pool).BASE_POOL() + base_lp_token: address = StableSwapNG(pool).coins(1) rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -178,7 +184,12 @@ def get_dy_underlying( # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() x = self._base_calc_token_amount( - dx, base_i, base_n_coins, BASE_POOL, True + dx, + base_i, + base_n_coins, + BASE_POOL, + base_lp_token, + True, ) * rates[1] / PRECISION # Adding number of pool tokens @@ -382,35 +393,43 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin @internal @view def _calc_token_amount( - _amounts: uint256[MAX_COINS], - is_deposit: bool, + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool, pool: address, pool_lp_token: address, n_coins: uint256, ) -> uint256: amp: uint256 = StableSwapNG(pool).A() * A_PRECISION - N_COINS: uint256 = StableSwapNG(pool).N_COINS() rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) old_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) + pool_is_ng: bool = raw_call( + pool, + method_id("D_ma_time()"), + revert_on_failure=False, + is_static_call=True + ) + use_dynamic_fees: bool = True if pool_is_ng: - rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) + rates, old_balances, xp = self._get_rates_balances_xp(pool, n_coins) else: - ... # TODO: accommodate if pool is not ng (but: cannot fully accommodate in a simple manner) + use_dynamic_fees = False + for i in range(n_coins, bound=MAX_COINS): + rates.append( + 10 ** (36 - convert(ERC20Detailed(StableSwapNG(pool).coins(i)).decimals(), uint256)) + ) + old_balances.append(StableSwapNG(pool).balances(i)) + xp.append(rates[i] * old_balances[i] / PRECISION) # Initial invariant - D0: uint256 = self.get_D(xp, amp, N_COINS) + D0: uint256 = self.get_D(xp, amp, n_coins) - total_supply: uint256 = StableSwapNG(pool).totalSupply() + total_supply: uint256 = StableSwapNG(pool_lp_token).totalSupply() new_balances: DynArray[uint256, MAX_COINS] = old_balances - for i in range(MAX_COINS): - if i == N_COINS: - break - + for i in range(n_coins, bound=MAX_COINS): amount: uint256 = _amounts[i] if _is_deposit: new_balances[i] += amount @@ -418,27 +437,26 @@ def _calc_token_amount( new_balances[i] -= amount # Invariant after change - for idx in range(MAX_COINS): - if idx == N_COINS: - break + for idx in range(n_coins, bound=MAX_COINS): xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D1: uint256 = self.get_D(xp, amp, N_COINS) + D1: uint256 = self.get_D(xp, amp, n_coins) # We need to recalculate the invariant accounting for fees # to calculate fair user's share D2: uint256 = D1 + fee_multiplier: uint256 = 0 + _dynamic_fee_i: uint256 = 0 if total_supply > 0: # Only account for fees if we are not the first to deposit - base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) - fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() - _dynamic_fee_i: uint256 = 0 + base_fee: uint256 = StableSwapNG(pool).fee() * n_coins / (4 * (n_coins - 1)) + if use_dynamic_fees: + fee_multiplier = StableSwapNG(pool).offpeg_fee_multiplier() + xs: uint256 = 0 - ys: uint256 = (D0 + D1) / N_COINS + ys: uint256 = (D0 + D1) / n_coins - for i in range(MAX_COINS): - if i == N_COINS: - break + for i in range(n_coins, bound=MAX_COINS): ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 @@ -449,15 +467,18 @@ def _calc_token_amount( difference = new_balance - ideal_balance xs = rates[i] * (old_balances[i] + new_balance) / PRECISION - _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) - new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR - for idx in range(MAX_COINS): - if idx == N_COINS: - break + # use dynamic fees only if pool is NG + if use_dynamic_fees: + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) + new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR + else: + new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + + for idx in range(n_coins, bound=MAX_COINS): xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D2 = self.get_D(xp, amp, N_COINS) + D2 = self.get_D(xp, amp, n_coins) else: return D1 # Take the dust if there was any @@ -480,8 +501,7 @@ def _base_calc_token_amount( is_deposit: bool, ) -> uint256: - base_pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) - base_inputs: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + base_inputs: DynArray[uint256, MAX_COINS] = [0, 0, 0, 0, 0, 0, 0, 0] base_inputs[base_i] = dx return self._calc_token_amount( diff --git a/poetry.lock b/poetry.lock index 69fcafa9..fab4bf8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3135,34 +3135,14 @@ typing-extensions = "*" vyper = ">=0.3.10" [package.extras] +colab = ["ipykernel (>=6.29.4)"] forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/DanielSchiavini/titanoboa" -reference = "zksync" -resolved_reference = "f96fff3450c060b6849a632608377b4477667884" - -[[package]] -name = "titanoboa-zksync" -version = "0.0.1" -description = "A Zksync plugin for the Titanoboa Vyper interpreter" -optional = false -python-versions = "*" -files = [] -develop = false - -[package.dependencies] -titanoboa = {git = "https://github.com/DanielSchiavini/titanoboa", rev = "zksync"} - -[package.extras] -forking-recommended = ["ujson"] - -[package.source] -type = "git" -url = "https://github.com/DanielSchiavini/titanoboa-zksync.git" -reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" -resolved_reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" +url = "https://github.com/vyperlang/titanoboa.git" +reference = "9757133904e2b8c2d79650d5713287749f269df0" +resolved_reference = "9757133904e2b8c2d79650d5713287749f269df0" [[package]] name = "tomli" @@ -3473,4 +3453,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "251db9973fb842214b8fa1401ec565f64f6e82e3aac76755a4a26fab226fca05" +content-hash = "ed178f29e080861b28abc40727265b8dcb4766be69d9dcca562d1ad37effe556" diff --git a/pyproject.toml b/pyproject.toml index 0d8ca2c8..f5c32613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ poetry = "1.5.1" vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" -titanoboa-zksync = {git = "https://github.com/DanielSchiavini/titanoboa-zksync.git", rev = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "9757133904e2b8c2d79650d5713287749f269df0"} [tool.poetry.group.dev.dependencies] black = "22.3.0"