diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3cc8b0..cd36aa9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,6 +21,3 @@ repos: hooks: - id: isort args: ["--profile", "black", --line-length=79] - -default_language_version: - python: python3.11 diff --git a/README.md b/README.md index 6c788ee..957f2de 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Mainnet: 1. Curve Stable Registry: A registry of custom pool implementations deployed by Curve Core. 2. Curve Stable Factory: A permissionless [StableSwap](https://curve.fi/files/stableswap-paper.pdf) pool factory, which also acts as a registry for pools that its users create. -3. Curve Crypto Registry: A registry of custom CryptoSwap pool implementaions deployed by Curve Core. +3. Curve Crypto Registry: A registry of custom CryptoSwap pool implementations deployed by Curve Core. 4. Curve Crypto Factory: A permissionless [CryptoSwap](https://curve.fi/files/crypto-pools-paper.pdf) pool factory, which also acts as a registry for pools that its users create. Each of the child registries are accompanied by a RegistryHandler, which is a contract that wraps around the child registry and enforces the abi implemented in the MetaRegistry. These registry handlers are then added to the MetaRegistry using the `MetaRegistry.add_registry_handler` method. @@ -67,9 +67,9 @@ Out[1]: '3pool' #### `MetaRegistry.is_meta` -Metapools are pools that pair a coin to a base pool comprising of multiple coins. +Meta-pools are pools that pair a coin to a base pool comprising multiple coins. -An example is the [`LUSD-3CRV`](https://etherscan.io/address/0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca) pool which pairs [Liquity's](https://www.liquity.org/) [`LUSD`](https://etherscan.io/address/0x5f98805a4e8be255a32880fdec7f6728c6568ba0) against [`3CRV`](https://etherscan.io/address/0x6c3f90f043a72fa612cbac8115ee7e52bde6e490), where `3CRV` is a liquidity pool token that represents a share of a pool containing `DAI`, `USDC` and `USDT`: +An example is the [`LUSD-3CRV`](https://etherscan.io/address/0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca) pool which pairs [Liquidity's](https://www.liquity.org/) [`LUSD`](https://etherscan.io/address/0x5f98805a4e8be255a32880fdec7f6728c6568ba0) against [`3CRV`](https://etherscan.io/address/0x6c3f90f043a72fa612cbac8115ee7e52bde6e490), where `3CRV` is a liquidity pool token that represents a share of a pool containing `DAI`, `USDC` and `USDT`: ``` In [1]: metaregistry.is_meta("0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca") @@ -294,7 +294,7 @@ For CryptoSwap, the getter returns: 4. Allowed extra profit 5. Fee gamma 6. Adjustment step -7. MA (moving average) half time +7. MA (moving average) half-time ``` @@ -338,7 +338,7 @@ Out[1]: '0xc4AD29ba4B3c580e6D59105FFf484999997675Ff' #### `MetaRegistry.get_pool_asset_type` -Gets the asset type of a pool. `0` = `USD`, `1` = `ETH`, `2` = `BTC`, `3` = Other, `4` = CryptoPool token. The asset type is a property of StableSwaps, and is not enforced in CryptoSwap pools (which always return `4`). +Gets the asset type of pool. `0` = `USD`, `1` = `ETH`, `2` = `BTC`, `3` = Other, `4` = CryptoPool token. The asset type is a property of StableSwaps, and is not enforced in CryptoSwap pools (which always return `4`). StableSwap pool example for `LUSD-3CRV` pool which is a `USD` stablecoin pool: diff --git a/scripts/change_registry_handler.py b/scripts/change_registry_handler.py index 7da239b..205afde 100644 --- a/scripts/change_registry_handler.py +++ b/scripts/change_registry_handler.py @@ -3,7 +3,12 @@ import boa from rich.console import Console as RichConsole -from scripts.deployment_utils import setup_environment, ZERO_ADDRESS, get_deployed_contract +from scripts.deployment_utils import ( + ADDRESS_PROVIDER, + ZERO_ADDRESS, + get_deployed_contract, + setup_environment, +) RICH_CONSOLE = RichConsole(file=sys.stdout) CRYPTO_REGISTRY_HANDLER = "0x5f493fEE8D67D3AE3bA730827B34126CFcA0ae94" diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 7a4362b..b7e01f7 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -36,6 +36,7 @@ def setup_environment(console: RichConsole): boa.env.eoa = FIDDY_DEPLOYER return False + def get_deployed_contract(contract_name: str, address: str) -> VyperContract: """ Loads a contract and retrieves a deployed instance of it with the given address. diff --git a/scripts/setup_metaregistry.py b/scripts/setup_metaregistry.py index 20e72bd..6427bbe 100644 --- a/scripts/setup_metaregistry.py +++ b/scripts/setup_metaregistry.py @@ -13,7 +13,11 @@ import boa from rich.console import Console as RichConsole -from scripts.deployment_utils import ZERO_ADDRESS, get_deployed_contract, setup_environment +from scripts.deployment_utils import ( + ZERO_ADDRESS, + get_deployed_contract, + setup_environment, +) RICH_CONSOLE = RichConsole(file=sys.stdout) diff --git a/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py b/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py index fce1773..35e1ef4 100644 --- a/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py +++ b/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py @@ -1,4 +1,7 @@ -import itertools +from itertools import combinations +from os import environ + +import pytest from tests.utils import ZERO_ADDRESS @@ -11,10 +14,14 @@ def _get_all_combinations(metaregistry, pool): pool_coins = [ coin for coin in metaregistry.get_coins(pool) if coin != ZERO_ADDRESS ] - all_combinations = list(itertools.combinations(pool_coins, 2)) + all_combinations = list(combinations(pool_coins, 2)) first_coin = pool_coins[0] - if metaregistry.is_meta(pool): + # there exist some pools with an LP token as the first coin, that's incorrect + # example: 0xf5d5305790c1af08e9df44b30a1afe56ccda72df + is_first_coin_lp_token = metaregistry.get_pool_from_lp_token(first_coin) + + if metaregistry.is_meta(pool) and not is_first_coin_lp_token: underlying_coins = [ coin for coin in metaregistry.get_underlying_coins(pool) @@ -29,16 +36,18 @@ def _get_all_combinations(metaregistry, pool): return all_combinations +@pytest.mark.skipif( + condition=environ.get("TEST_ALL") == "False", + reason="This test is too slow, don't run it locally every time.", +) def test_all(populated_metaregistry, pool): - combinations = _get_all_combinations(populated_metaregistry, pool) - for combination in combinations: + all_combinations = _get_all_combinations(populated_metaregistry, pool) + for coin1, coin2 in all_combinations: pools_containing_pair = populated_metaregistry.find_pools_for_coins( - *combination + coin1, coin2 ) assert pool in pools_containing_pair for i, found_pool in enumerate(pools_containing_pair): - assert ( - populated_metaregistry.find_pool_for_coins(*combination, i) - == found_pool - ) + pool = populated_metaregistry.find_pool_for_coins(coin1, coin2, i) + assert pool == found_pool diff --git a/tests/mainnet/metaregistry/api/test_get_admin_balances.py b/tests/mainnet/metaregistry/api/test_get_admin_balances.py index 32f4122..7798571 100644 --- a/tests/mainnet/metaregistry/api/test_get_admin_balances.py +++ b/tests/mainnet/metaregistry/api/test_get_admin_balances.py @@ -3,7 +3,11 @@ from boa import BoaError from eth.codecs.abi.exceptions import DecodeError as ABIDecodeError -from tests.utils import ZERO_ADDRESS, get_deployed_token_contract +from tests.utils import ( + assert_negative_coin_balance, + check_decode_error, + get_deployed_token_contract, +) def pre_test_checks(metaregistry, pool): @@ -29,31 +33,12 @@ def pre_test_checks(metaregistry, pool): if contract.totalSupply() == 0: return pytest.skip("LP token supply is zero") except ABIDecodeError as e: - assert e.msg == "Value length is not the expected size of 32 bytes" - assert len(e.value) == 4096 + check_decode_error(e) return pytest.skip( f"Pool {pool} cannot decode the total supply of its LP token {lp_token}" ) -def assert_negative_coin_balance(metaregistry, pool): - """ - The implementation of get_balance calculates (balance - admin_balance) but sometimes the coin - balance might be lower than the admin balance, resulting in an uint underflow. - """ - coins = [ - coin for coin in metaregistry.get_coins(pool) if coin != ZERO_ADDRESS - ] - coin_balances = [ - get_deployed_token_contract(coin).balanceOf(pool) for coin in coins - ] - admin_balances = metaregistry.get_admin_balances(pool) - assert any( - coin_balance < admin_balance - for coin_balance, admin_balance in zip(coin_balances, admin_balances) - ) - - def test_stable_registry_pools( populated_metaregistry, stable_registry_pool, stable_registry ): diff --git a/tests/mainnet/metaregistry/api/test_get_underlying_decimals.py b/tests/mainnet/metaregistry/api/test_get_underlying_decimals.py index 264ef97..a46ae7d 100644 --- a/tests/mainnet/metaregistry/api/test_get_underlying_decimals.py +++ b/tests/mainnet/metaregistry/api/test_get_underlying_decimals.py @@ -17,7 +17,6 @@ def _test_underlying_decimals_getter(metaregistry, registry, pool): metaregistry_output = metaregistry.get_underlying_decimals(pool) - assert metaregistry_output[1] != 0 # there has to be a second coin! pool_is_metapool = metaregistry.is_meta(pool) if pool in EXCEPTIONS: diff --git a/tests/mainnet/metaregistry/api/test_get_virtual_price.py b/tests/mainnet/metaregistry/api/test_get_virtual_price.py index cc6c858..4c4c216 100644 --- a/tests/mainnet/metaregistry/api/test_get_virtual_price.py +++ b/tests/mainnet/metaregistry/api/test_get_virtual_price.py @@ -3,8 +3,14 @@ import boa import pytest from boa import BoaError +from eth.codecs.abi.exceptions import DecodeError as ABIDecodeError -from tests.utils import ZERO_ADDRESS, get_deployed_token_contract +from tests.utils import ( + ZERO_ADDRESS, + assert_negative_coin_balance, + check_decode_error, + get_deployed_token_contract, +) # ---- sanity checks since vprice getters can revert for specific pools states ---- @@ -36,18 +42,21 @@ def _check_skem_tokens_with_weird_decimals( pool_balances_float.append(pool_balances[i] / 10 ** coin_decimals[i]) - if ( - coin_decimals[i] == 0 - and get_deployed_token_contract( - metaregistry.get_coins(pool)[0] - ).decimals() - == 0 - ): - with boa.reverts(): + first_coin = metaregistry.get_coins(pool)[0] + coin_contract = get_deployed_token_contract(first_coin) + if coin_decimals[i] == 0 and coin_contract.decimals() == 0: + try: + virtual_price = metaregistry.get_virtual_price_from_lp_token( + lp_token + ) + warnings.warn( + f"Pool {pool} virtual price {virtual_price}. continuing test" + ) + except BoaError: metaregistry.get_virtual_price_from_lp_token(lp_token) - pytest.skip( - f"skem token {coins[i]} in pool {pool} with zero decimals" - ) + pytest.skip( + f"Skem token {coins[i]} in pool {pool} with zero decimals" + ) return pool_balances_float @@ -69,23 +78,26 @@ def _check_pool_is_depegged( and min(pool_balances_float) < 1 ): try: - with boa.reverts(): - metaregistry.get_virtual_price_from_lp_token(lp_token) - + virtual_price = metaregistry.get_virtual_price_from_lp_token( + lp_token + ) + warnings.warn( + f"Pool {pool} virtual price {virtual_price}. continuing test" + ) + except BoaError: pytest.skip( f"skewed pool: {pool} as num coins (decimals divided) at index {i} is " f"{pool_balances[i] / 10 ** coin_decimals[i]}" ) - except ( - AssertionError - ): # ok to catch this assertion error since we continue testing - warnings.warn( - "pool virtual price getter did not revert. continuing test" - ) def pre_test_checks(metaregistry, pool): - pool_balances = metaregistry.get_balances(pool) + try: + pool_balances = metaregistry.get_balances(pool) + except BoaError: + assert_negative_coin_balance(metaregistry, pool) + return pytest.skip(f"Pool {pool} has coin balances lower than admin") + lp_token = metaregistry.get_lp_token(pool) _check_pool_has_no_liquidity(metaregistry, pool, pool_balances, lp_token) @@ -139,6 +151,11 @@ def test_stable_factory_pools( except BoaError: with boa.reverts(): populated_metaregistry.get_virtual_price_from_lp_token(lp_token) + except ABIDecodeError as e: + check_decode_error(e) + return pytest.skip( + f"Pool {stable_factory_pool} cannot decode the virtual price" + ) def test_crypto_registry_pools( diff --git a/tests/utils.py b/tests/utils.py index 57ced7c..82c13ad 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,6 +4,7 @@ import boa from boa.vyper.contract import VyperContract +from eth.codecs.abi.exceptions import DecodeError as ABIDecodeError from eth_account.signers.local import LocalAccount ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -98,3 +99,31 @@ def get_deployed_token_contract(address: str) -> VyperContract: ] ) return boa.loads_abi(abi).at(address) + + +def check_decode_error(e: ABIDecodeError): + """ + Checks that the error message is the expected decode error. + This seems to be happening in some pools, but it's not clear if it's a boa or contract issue. + :param e: The error to check. + """ + assert e.msg == "Value length is not the expected size of 32 bytes" + assert len(e.value) == 4096 + + +def assert_negative_coin_balance(metaregistry, pool): + """ + The implementation of get_balance calculates (balance - admin_balance) but sometimes the coin + balance might be lower than the admin balance, resulting in an uint underflow. + """ + coins = [ + coin for coin in metaregistry.get_coins(pool) if coin != ZERO_ADDRESS + ] + coin_balances = [ + get_deployed_token_contract(coin).balanceOf(pool) for coin in coins + ] + admin_balances = metaregistry.get_admin_balances(pool) + assert any( + coin_balance < admin_balance + for coin_balance, admin_balance in zip(coin_balances, admin_balances) + )