Skip to content

Commit

Permalink
Merge branch 'larger-gamma' of github.com:curvefi/twocrypto-ng into l…
Browse files Browse the repository at this point in the history
…arger-gamma
  • Loading branch information
AlbertoCentonze committed May 27, 2024
2 parents 391d378 + 54c9fb7 commit cbcc0c9
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 41 deletions.
123 changes: 123 additions & 0 deletions contracts/experimental/CurveCryptoMath2MAX.vy
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down
13 changes: 4 additions & 9 deletions contracts/experimental/initial_guess/CurveTwocryptoOptimized.vy
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
)

Expand Down
45 changes: 25 additions & 20 deletions tests/profiling/conftest.py
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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:
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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


Expand Down Expand Up @@ -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,
]
24 changes: 14 additions & 10 deletions tests/profiling/test_boa_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@

from tests.utils.tokens import mint_for_testing


NUM_RUNS = 10
NUM_RUNS = 100
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)
Expand All @@ -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:
Expand All @@ -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)

0 comments on commit cbcc0c9

Please sign in to comment.