From 285d31e381b4f4043fbc19bfbb79ead42a2b9484 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 27 May 2024 15:10:45 +0200 Subject: [PATCH 1/2] fix profiling --- .../CurveCryptoMathOptimized2.vy | 2 +- .../initial_guess/CurveTwocryptoOptimized.vy | 13 ++---- tests/profiling/conftest.py | 45 ++++++++++--------- tests/profiling/test_boa_profile.py | 22 +++++---- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy b/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy index e5602dc4..7608217b 100644 --- a/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy +++ b/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy @@ -398,7 +398,7 @@ def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS], initial if D == 0: D = N_COINS * isqrt(unsafe_mul(x[0], x[1])) else: - # initial_DD = isqrt(x[0] * x[1] * 4 / K0_prev * 10**18) + # initial_D = isqrt(x[0] * x[1] * 4 / K0_prev * 10**18) # K0_prev is derived from from get_y if S < D: D = S diff --git a/contracts/experimental/initial_guess/CurveTwocryptoOptimized.vy b/contracts/experimental/initial_guess/CurveTwocryptoOptimized.vy index 0a8e22b6..28879303 100644 --- a/contracts/experimental/initial_guess/CurveTwocryptoOptimized.vy +++ b/contracts/experimental/initial_guess/CurveTwocryptoOptimized.vy @@ -805,12 +805,7 @@ def _exchange( # ------ Tweak price_scale with good initial guess for newton_D ---------- # Get initial guess using: D = isqrt(x[0] * x[1] * 4 / K0_prev * 10**18) - initial_D: uint256 = isqrt( - unsafe_mul( - unsafe_div(unsafe_mul(unsafe_mul(4, xp[0]), xp[1]), y_out[1]), - 10**18 - ) - ) + initial_D: uint256 = isqrt(xp[0] * xp[1] * 4 / y_out[1] * 10**18) price_scale = self.tweak_price(A_gamma, xp, 0, initial_D) return [dy, fee, price_scale] @@ -971,9 +966,9 @@ def tweak_price( # ------------------------------------------ Update D with new xp. D: uint256 = MATH.newton_D( - A_gamma[0], - A_gamma[1], - xp, + A_gamma[0], + A_gamma[1], + xp, D_unadjusted ) diff --git a/tests/profiling/conftest.py b/tests/profiling/conftest.py index c6817fa5..63a69cad 100644 --- a/tests/profiling/conftest.py +++ b/tests/profiling/conftest.py @@ -1,17 +1,20 @@ import boa import pytest - from hypothesis import assume -from tests.utils.tokens import mint_for_testing + +from contracts.experimental.initial_guess import ( + CurveCryptoMathOptimized2 as math_deployer_initial_guess, +) +from contracts.experimental.initial_guess import ( + CurveTwocryptoOptimized as amm_deployer_initial_guess, +) # compiling contracts +from contracts.main import CurveCryptoMathOptimized2 as math_deployer from contracts.main import CurveCryptoViews2Optimized as view_deployer from contracts.main import CurveTwocryptoFactory as factory_deployer - from contracts.main import CurveTwocryptoOptimized as amm_deployer -from contracts.main import CurveCryptoMathOptimized2 as math_deployer -from contracts.experimental.initial_guess import CurveTwocryptoOptimized as amm_deployer_initial_guess -from contracts.experimental.initial_guess import CurveCryptoMathOptimized2 as math_deployer_initial_guess +from tests.utils.tokens import mint_for_testing # ---------------- addresses ---------------- address = boa.test.strategy("address") @@ -32,10 +35,12 @@ def _deposit_initial_liquidity(pool, tokens): - + # deposit: user = boa.env.generate_address() - quantities = [10**6 * 10**36 // p for p in [10**18, params["price"]]] # $2M worth + quantities = [ + 10**6 * 10**36 // p for p in [10**18, params["price"]] + ] # $2M worth for coin, quantity in zip(tokens, quantities): # mint coins for user: @@ -49,21 +54,21 @@ def _deposit_initial_liquidity(pool, tokens): # Very first deposit with boa.env.prank(user): pool.add_liquidity(quantities, 0) - + return pool @pytest.fixture(scope="module") def tokens(): return [ - boa.load("contracts/mocks/ERC20Mock.vy", "tkn_a", "tkn_a", 18), - boa.load("contracts/mocks/ERC20Mock.vy", "tkn_b", "tkn_b", 18) -] + boa.load("contracts/mocks/ERC20Mock.vy", "tkn_a", "tkn_a", 18), + boa.load("contracts/mocks/ERC20Mock.vy", "tkn_b", "tkn_b", 18), + ] @pytest.fixture(scope="module") def factory_no_initial_guess(): - + _deployer = boa.env.generate_address() _fee_receiver = boa.env.generate_address() _owner = boa.env.generate_address() @@ -80,16 +85,16 @@ def factory_no_initial_guess(): with boa.env.prank(_owner): _factory.set_views_implementation(view_contract) _factory.set_math_implementation(math_contract) - + # set pool implementations: _factory.set_pool_implementation(amm_implementation, 0) - + return _factory @pytest.fixture(scope="module") def factory_initial_guess(): - + _deployer = boa.env.generate_address() _fee_receiver = boa.env.generate_address() _owner = boa.env.generate_address() @@ -107,10 +112,10 @@ def factory_initial_guess(): with boa.env.prank(_owner): _factory.set_views_implementation(view_contract) _factory.set_math_implementation(math_contract) - + # set pool implementations: _factory.set_pool_implementation(amm_implementation, 0) - + return _factory @@ -165,6 +170,6 @@ def pool_initial_guess(factory_initial_guess, tokens): @pytest.fixture(scope="module") def pools(pool, pool_initial_guess): return [ - pool_initial_guess, - pool, + pool, + # pool_initial_guess, ] diff --git a/tests/profiling/test_boa_profile.py b/tests/profiling/test_boa_profile.py index 82954b7b..dd459f88 100644 --- a/tests/profiling/test_boa_profile.py +++ b/tests/profiling/test_boa_profile.py @@ -5,24 +5,23 @@ from tests.utils.tokens import mint_for_testing - NUM_RUNS = 10 N_COINS = 2 def _choose_indices(): - i = random.randint(0, N_COINS-1) + i = random.randint(0, N_COINS - 1) j = 0 if i == 1 else 1 return i, j -@pytest.mark.profile +@pytest.mark.gas_profile def test_profile_amms(pools, tokens): - + user = boa.env.generate_address() for pool in pools: - + for coin in tokens: mint_for_testing(coin, user, 10**50) coin.approve(pool, 2**256 - 1, sender=user) @@ -33,15 +32,20 @@ def test_profile_amms(pools, tokens): # proportional deposit: balances = [pool.balances(i) for i in range(N_COINS)] - amount_first_coin = random.uniform(0, 0.05) * 10**(18+random.randint(1, 3)) - amounts = [int(amount_first_coin), int(amount_first_coin * 1e18 // pool.price_scale())] + amount_first_coin = random.uniform(0, 0.05) * 10 ** ( + 18 + random.randint(1, 3) + ) + amounts = [ + int(amount_first_coin), + int(amount_first_coin * 1e18 // pool.price_scale()), + ] pool.add_liquidity(amounts, 0) boa.env.time_travel(random.randint(12, 600)) # deposit single token: balances = [pool.balances(i) for i in range(N_COINS)] c = random.uniform(0, 0.05) - i = random.randint(0, N_COINS-1) + i = random.randint(0, N_COINS - 1) amounts = [0] * N_COINS for j in range(N_COINS): if i == j: @@ -61,6 +65,6 @@ def test_profile_amms(pools, tokens): boa.env.time_travel(random.randint(12, 600)) # withdraw in one coin: - i = random.randint(0, N_COINS-1) + i = random.randint(0, N_COINS - 1) amount = int(pool.balanceOf(user) * 0.01) pool.remove_liquidity_one_coin(amount, i, 0) From 54c9fb7f9f0f27c3d8c29f5ccfcfb44724bfface Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 27 May 2024 15:54:16 +0200 Subject: [PATCH 2/2] add max newton_D experimental contract --- contracts/experimental/CurveCryptoMath2MAX.vy | 123 ++++++++++++++++++ .../CurveCryptoMathOptimized2.vy | 9 +- tests/profiling/test_boa_profile.py | 2 +- 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 contracts/experimental/CurveCryptoMath2MAX.vy diff --git a/contracts/experimental/CurveCryptoMath2MAX.vy b/contracts/experimental/CurveCryptoMath2MAX.vy new file mode 100644 index 00000000..19376a21 --- /dev/null +++ b/contracts/experimental/CurveCryptoMath2MAX.vy @@ -0,0 +1,123 @@ +# pragma version 0.3.10 +# pragma optimize gas +# pragma evm-version paris + + +N_COINS: constant(uint256) = 2 +A_MULTIPLIER: constant(uint256) = 10000 + +MIN_GAMMA: constant(uint256) = 10**10 +MAX_GAMMA_SMALL: constant(uint256) = 2 * 10**16 +MAX_GAMMA: constant(uint256) = 199 * 10**15 # 1.99 * 10**17 + +MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 10 +MAX_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER * 1000 + +MAX_ITER: constant(uint256) = 255 + + +@external +@view +def newton_D() -> ( + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER], + uint256[MAX_ITER] +): + """ + Finding the invariant using Newton method. + ANN is higher by the factor A_MULTIPLIER + ANN is already A * N**N + """ + + # # Safety checks + # assert ANN > MIN_A - 1 and ANN < MAX_A + 1 # dev: unsafe values A + # assert gamma > MIN_GAMMA - 1 and gamma < MAX_GAMMA + 1 # dev: unsafe values gamma + + # # Initial value of invariant D is that for constant-product invariant + # x: uint256[N_COINS] = x_unsorted + # if x[0] < x[1]: + # x = [x_unsorted[1], x_unsorted[0]] + + # assert x[0] > 10**9 - 1 and x[0] < 10**15 * 10**18 + 1 # dev: unsafe values x[0] + # assert unsafe_div(x[1] * 10**18, x[0]) > 10**14 - 1 # dev: unsafe values x[i] (input) + + ANN: uint256 = MAX_A + gamma: uint256 = MAX_GAMMA + + x: uint256[N_COINS] = [10**15 * 10**18, 10**15 * 10**18] + S: uint256 = unsafe_add(x[0], x[1]) # can unsafe add here because we checked x[0] bounds + D: uint256 = N_COINS * isqrt(unsafe_mul(x[0], x[1])) + + __g1k0: uint256 = gamma + 10**18 + diff: uint256 = 0 + + K0_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + _g1k0_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + mul1_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + mul2_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + neg_fprime_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + D_plus_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + D_minus_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + D_iter: uint256[MAX_ITER] = empty(uint256[MAX_ITER]) + + for i in range(MAX_ITER): + D_prev: uint256 = D + assert D > 0 + # Unsafe division by D and D_prev is now safe + + # K0: uint256 = 10**18 + # for _x in x: + # K0 = K0 * _x * N_COINS / D + # collapsed for 2 coins + K0: uint256 = unsafe_div(unsafe_div((10**18 * N_COINS**2) * x[0], D) * x[1], D) + + _g1k0: uint256 = __g1k0 + if _g1k0 > K0: + _g1k0 = unsafe_add(unsafe_sub(_g1k0, K0), 1) # > 0 + else: + _g1k0 = unsafe_add(unsafe_sub(K0, _g1k0), 1) # > 0 + # K0 is greater than 0 + # _g1k0 is greater than 0 + + # D / (A * N**N) * _g1k0**2 / gamma**2 + mul1: uint256 = unsafe_div(unsafe_div(unsafe_div(10**18 * D, gamma) * _g1k0, gamma) * _g1k0 * A_MULTIPLIER, ANN) + + # 2*N*K0 / _g1k0 + mul2: uint256 = unsafe_div(((2 * 10**18) * N_COINS) * K0, _g1k0) + + # calculate neg_fprime. here K0 > 0 is being validated (safediv). + neg_fprime: uint256 = ( + S + + unsafe_div(S * mul2, 10**18) + + mul1 * N_COINS / K0 - + unsafe_div(mul2 * D, 10**18) + ) + + # D -= f / fprime; neg_fprime safediv being validated + D_plus: uint256 = D * (neg_fprime + S) / neg_fprime + D_minus: uint256 = unsafe_div(D * D, neg_fprime) + if 10**18 > K0: + D_minus += unsafe_div(unsafe_div(D * unsafe_div(mul1, neg_fprime), 10**18) * unsafe_sub(10**18, K0), K0) + else: + D_minus -= unsafe_div(unsafe_div(D * unsafe_div(mul1, neg_fprime), 10**18) * unsafe_sub(K0, 10**18), K0) + + if D_plus > D_minus: + D = unsafe_sub(D_plus, D_minus) + else: + D = unsafe_div(unsafe_sub(D_minus, D_plus), 2) + + K0_iter[i] = K0 + _g1k0_iter[i] = _g1k0 + mul1_iter[i] = mul1 + mul2_iter[i] = mul2 + neg_fprime_iter[i] = neg_fprime + D_plus_iter[i] = D_plus + D_minus_iter[i] = D_minus + D_iter[i] = D + + return K0_iter, _g1k0_iter, mul1_iter, mul2_iter, neg_fprime_iter, D_plus_iter, D_minus_iter, D_iter diff --git a/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy b/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy index 7608217b..1c00f83a 100644 --- a/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy +++ b/contracts/experimental/initial_guess/CurveCryptoMathOptimized2.vy @@ -422,6 +422,8 @@ def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS], initial _g1k0 = unsafe_add(unsafe_sub(_g1k0, K0), 1) # > 0 else: _g1k0 = unsafe_add(unsafe_sub(K0, _g1k0), 1) # > 0 + # K0 is greater than 0 + # _g1k0 is greater than 0 # D / (A * N**N) * _g1k0**2 / gamma**2 mul1: uint256 = unsafe_div(unsafe_div(unsafe_div(10**18 * D, gamma) * _g1k0, gamma) * _g1k0 * A_MULTIPLIER, ANN) @@ -430,7 +432,12 @@ def newton_D(ANN: uint256, gamma: uint256, x_unsorted: uint256[N_COINS], initial mul2: uint256 = unsafe_div(((2 * 10**18) * N_COINS) * K0, _g1k0) # calculate neg_fprime. here K0 > 0 is being validated (safediv). - neg_fprime: uint256 = (S + unsafe_div(S * mul2, 10**18)) + mul1 * N_COINS / K0 - unsafe_div(mul2 * D, 10**18) + neg_fprime: uint256 = ( + S + + unsafe_div(S * mul2, 10**18) + + mul1 * N_COINS / K0 - + unsafe_div(mul2 * D, 10**18) + ) # D -= f / fprime; neg_fprime safediv being validated D_plus: uint256 = D * (neg_fprime + S) / neg_fprime diff --git a/tests/profiling/test_boa_profile.py b/tests/profiling/test_boa_profile.py index dd459f88..cb49ca54 100644 --- a/tests/profiling/test_boa_profile.py +++ b/tests/profiling/test_boa_profile.py @@ -5,7 +5,7 @@ from tests.utils.tokens import mint_for_testing -NUM_RUNS = 10 +NUM_RUNS = 100 N_COINS = 2