Skip to content

Commit

Permalink
allow ng pools as base pools
Browse files Browse the repository at this point in the history
  • Loading branch information
bout3fiddy committed Dec 8, 2023
1 parent 9bace0d commit 4b3cb6d
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 149 deletions.
24 changes: 21 additions & 3 deletions contracts/main/CurveStableSwapMetaNG.vy
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ interface StableSwap2:
interface StableSwap3:
def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable

interface StableSwapNG:
def add_liquidity(
amounts: DynArray[uint256, MAX_COINS],
min_mint_amount: uint256
) -> uint256: nonpayable

interface StableSwap:
def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable
def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable
Expand Down Expand Up @@ -201,6 +207,7 @@ N_COINS_128: constant(int128) = 2
PRECISION: constant(uint256) = 10 ** 18

BASE_POOL: public(immutable(address))
BASE_POOL_IS_NG: immutable(bool)
BASE_N_COINS: public(immutable(uint256))
BASE_COINS: public(immutable(DynArray[address, MAX_COINS]))

Expand Down Expand Up @@ -326,10 +333,11 @@ def __init__(
Calculated as: keccak(text=event_signature.replace(" ", ""))[:4]
@param _oracles Array of rate oracle addresses.
"""
assert len(_base_coins) <= 3 # dev: implementation does not support base pool with more than 3 coins

# The following reverts if BASE_POOL is an NG implementaion.
assert not raw_call(_base_pool, method_id("D_ma_time()"), revert_on_failure=False)
BASE_POOL_IS_NG = raw_call(_base_pool, method_id("D_ma_time()"), revert_on_failure=False)

if not BASE_POOL_IS_NG:
assert len(_base_coins) <= 3 # dev: implementation does not support old gen base pool with more than 3 coins

math = Math(_math_implementation)
BASE_POOL = _base_pool
Expand Down Expand Up @@ -1179,6 +1187,16 @@ def _exchange(
@internal
def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256:

if BASE_POOL_IS_NG:

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).add_liquidity(base_inputs, 0)

coin_i: address = coins[MAX_METAPOOL_COIN_INDEX]
x: uint256 = ERC20(coin_i).balanceOf(self)

Expand Down
2 changes: 1 addition & 1 deletion contracts/main/CurveStableSwapNG.vy
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256:

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

Expand Down
8 changes: 7 additions & 1 deletion 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
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ packages = []
[tool.poetry.dependencies]
python = "^3.10"
poetry = "1.5.1"
titanoboa = {git = "https://github.com/vyperlang/titanoboa", rev = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47"}
titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0"}
vyper = "0.3.10"
pycryptodome = "^3.18.0"
pre-commit = "^3.3.3"
Expand Down
12 changes: 6 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals):
#
# @pytest.mark.only_for_token_types(2)
# class TestPoolsWithOracleToken:
@pytest.fixture(autouse=True)
@pytest.fixture()
def skip_by_token_type(request, pool_tokens):
only_for_token_types = request.node.get_closest_marker("only_for_token_types")
if only_for_token_types:
Expand All @@ -179,15 +179,15 @@ def skip_by_token_type(request, pool_tokens):
pytest.skip("skipped because no tokens for these types")


@pytest.fixture(autouse=True)
@pytest.fixture()
def skip_rebasing(request, swap):
only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens")
if only_for_token_types:
if 2 in get_asset_types_in_pool(swap):
pytest.skip("skipped because test includes rebasing tokens")


@pytest.fixture(autouse=True)
@pytest.fixture()
def skip_oracle(request, pool_tokens):
only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens")
if only_for_token_types:
Expand All @@ -197,7 +197,7 @@ def skip_oracle(request, pool_tokens):
pytest.skip("skipped because test includes oraclised tokens")


@pytest.fixture(autouse=True)
@pytest.fixture()
def only_oracle(request, pool_tokens):
only_for_token_types = request.node.get_closest_marker("only_oracle_tokens")
if only_for_token_types:
Expand All @@ -207,7 +207,7 @@ def only_oracle(request, pool_tokens):
pytest.skip("skipped because test excludes oraclised tokens")


@pytest.fixture(autouse=True)
@pytest.fixture()
def only_rebasing(request, swap):
marker = request.node.get_closest_marker("contains_rebasing_tokens")
if marker:
Expand All @@ -219,7 +219,7 @@ def only_rebasing(request, swap):
# Usage
# @pytest.mark.only_for_pool_type(1)
# class TestMetaPool...
@pytest.fixture(autouse=True)
@pytest.fixture()
def skip_by_pool_type(request, pool_type):
only_for_pool_type = request.node.get_closest_marker("only_for_pool_type")
if only_for_pool_type:
Expand Down
2 changes: 1 addition & 1 deletion tests/gauge/test_rewards.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@pytest.mark.usefixtures("forked_chain")
class TestGaugeRewards:
class TestAddRewards:
@pytest.fixture(autouse=True)
@pytest.fixture()
def initial_setup(self, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation):
with boa.env.prank(owner):
swap.approve(gauge.address, LP_AMOUNT)
Expand Down
177 changes: 177 additions & 0 deletions tests/pools/meta/test_meta_new_ng_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import itertools

import boa
import pytest

from tests.utils.tokens import mint_for_testing

BASE_N_COINS = 5


@pytest.fixture(scope="module")
def ng_base_pool_decimals():
return [18] * BASE_N_COINS


@pytest.fixture(scope="module")
def ng_base_pool_tokens(ng_base_pool_decimals):
tokens = []
for i in range(BASE_N_COINS):
tokens.append(boa.load("contracts/mocks/ERC20.vy", f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]))

return tokens


@pytest.fixture(scope="module")
def meta_token():
return boa.load(
"contracts/mocks/ERC20.vy",
"OTA",
"OTA",
18,
)


@pytest.fixture(scope="module")
def ng_base_pool(
deployer,
factory,
ng_base_pool_tokens,
zero_address,
amm_interface,
set_pool_implementations,
):
pool_size = len(ng_base_pool_tokens)
offpeg_fee_multiplier = 20000000000
method_ids = [bytes(b"")] * pool_size
oracles = [zero_address] * pool_size
A = 1000
fee = 3000000

with boa.env.prank(deployer):
pool = factory.deploy_plain_pool(
"test",
"test",
[t.address for t in ng_base_pool_tokens],
A,
fee,
offpeg_fee_multiplier,
866,
0,
[tkn.asset_type() for tkn in ng_base_pool_tokens],
method_ids,
oracles,
)

return amm_interface.at(pool)


@pytest.fixture(scope="module")
def ng_metapool_tokens(meta_token, ng_base_pool):
return [meta_token, ng_base_pool]


@pytest.fixture(scope="module")
def add_ng_base_pool(
owner,
factory,
ng_base_pool,
ng_base_pool_tokens,
):
with boa.env.prank(owner):
factory.add_base_pool(
ng_base_pool.address,
ng_base_pool.address,
[0] * len(ng_base_pool_tokens),
len(ng_base_pool_tokens),
)


@pytest.fixture(scope="module")
def empty_swap(
deployer,
factory,
zero_address,
meta_token,
ng_base_pool,
amm_interface_meta,
add_ng_base_pool,
set_metapool_implementations,
):
method_id = bytes(b"")
oracle = zero_address
offpeg_fee_multiplier = 20000000000
A = 1000
fee = 3000000

with boa.env.prank(deployer):
pool = factory.deploy_metapool(
ng_base_pool.address, # _base_pool: address
"test", # _name: String[32],
"test", # _symbol: String[10],
meta_token.address, # _coin: address,
A, # _A: uint256,
fee, # _fee: uint256,
offpeg_fee_multiplier,
866, # _ma_exp_time: uint256,
0, # _implementation_idx: uint256
meta_token.asset_type(), # _asset_type: uint8
method_id, # _method_id: bytes4
oracle, # _oracle: address
)

return amm_interface_meta.at(pool)


@pytest.fixture(scope="module")
def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, ng_base_pool):
for token in [meta_token] + ng_base_pool_tokens:
mint_for_testing(bob, 10**25, token)
token.approve(empty_swap, 2**256 - 1, sender=bob)
token.approve(ng_base_pool, 2**256 - 1, sender=bob)


@pytest.fixture(scope="module")
def deposit_amounts(
meta_token,
ng_base_pool,
ng_base_pool_tokens,
ng_base_pool_decimals,
empty_swap,
bob,
mint_and_approve_for_bob,
):
_deposit_amounts = []
INITIAL_AMOUNT = 1_000_000 * BASE_N_COINS
_deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** meta_token.decimals())

def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals):
amount = INITIAL_AMOUNT // BASE_N_COINS
with boa.env.prank(user):
amounts = [amount * 10**d for d in base_pool_decimals]
base_pool.add_liquidity(amounts, 0)

add_base_pool_liquidity(bob, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals)
_deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** ng_base_pool.decimals())
ng_base_pool.approve(empty_swap, 2**256 - 1, sender=bob)
return _deposit_amounts


@pytest.fixture(scope="module")
def swap(empty_swap, bob, deposit_amounts):
empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob)
return empty_swap


@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2))
def test_exchange_underlying_ng_base(
swap,
bob,
sending,
receiving,
):
amount = 10**19
expected_out = swap.get_dy_underlying(sending, receiving, amount)
actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob)

assert expected_out == actual_out
Loading

0 comments on commit 4b3cb6d

Please sign in to comment.