diff --git a/scripts/change_registry_handler.py b/scripts/change_registry_handler.py index 7896e61..6bcbfad 100644 --- a/scripts/change_registry_handler.py +++ b/scripts/change_registry_handler.py @@ -1,52 +1,42 @@ import sys -from brownie import ( - AddressProvider, - CryptoRegistry, - MetaRegistry, - ProxyAdmin, - accounts, - network, -) -from brownie.network.gas.strategies import GasNowScalingStrategy +import boa from rich.console import Console as RichConsole +from scripts.deployment_utils import setup_environment +from tests.utils import ZERO_ADDRESS, get_deployed_contract + RICH_CONSOLE = RichConsole(file=sys.stdout) ADDRESS_PROVIDER = "0x0000000022D53366457F9d5E68Ec105046FC4383" CRYPTO_REGISTRY_HANDLER = "0x5f493fEE8D67D3AE3bA730827B34126CFcA0ae94" CRYPTO_REGISTRY_HANDLER_ID = 2 OLD_CRYPTO_REGISTRY_HANDLER = "0x22ceb131d3170f9f2FeA6b4b1dE1B45fcfC86E56" CRYPTO_REGISTRY = "0x9a32aF1A11D9c937aEa61A3790C2983257eA8Bc0" -ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" - +METAREGISTRY = "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC" -if network.show_active() == "mainnet": - RICH_CONSOLE.log("Writing on mainnet") - accounts.load("babe") - txparams = {"from": accounts[0], "required_confs": 5} - try: - network.gas_price(GasNowScalingStrategy("slow", "fast")) - except ConnectionError: - pass -else: - RICH_CONSOLE.log("Simulation Mode. Writing to mainnet-fork.") - txparams = { - "from": accounts.at( - "0xbabe61887f1de2713c6f97e567623453d3C79f67", force=True - ) - } +def setup(): + """ + Sets up the environment for deployment scripts. + :return: txparams for deployment. + """ + if setup_environment(RICH_CONSOLE): + return {"from": "0xbabe61887f1de2713c6f97e567623453d3C79f67"} + return {"from": boa.env.eoa, "required_confs": 5} -def main(): +def main(txparams) -> None: + """ + Change the registry handler of the metaregistry to the new crypto registry handler. + :param txparams: txparams argument for the proxy admin. + """ # admin only: only admin of ADDRESSPROVIDER's proxy admin can do the following: - address_provider = AddressProvider.at(ADDRESS_PROVIDER) - address_provider_admin = address_provider.admin() - proxy_admin = ProxyAdmin.at(address_provider_admin) - - metaregistry = MetaRegistry.at( - "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC" + address_provider = get_deployed_contract( + "AddressProvider", ADDRESS_PROVIDER ) + address_provider_admin = address_provider.admin() + proxy_admin = get_deployed_contract("ProxyAdmin", address_provider_admin) + metaregistry = get_deployed_contract("MetaRegistry", METAREGISTRY) assert ( metaregistry.get_registry(CRYPTO_REGISTRY_HANDLER_ID) @@ -54,9 +44,8 @@ def main(): ) num_pools = metaregistry.pool_count() num_registries = metaregistry.registry_length() - num_pools_in_crypto_registry = CryptoRegistry.at( - CRYPTO_REGISTRY - ).pool_count() + crypto_registry = get_deployed_contract("CryptoRegistry", CRYPTO_REGISTRY) + num_pools_in_crypto_registry = crypto_registry.pool_count() call_data = metaregistry.update_registry_handler.encode_input( CRYPTO_REGISTRY_HANDLER_ID, @@ -68,11 +57,11 @@ def main(): metaregistry.pool_count() == num_pools + num_pools_in_crypto_registry ) assert metaregistry.registry_length() == num_registries - assert ( - metaregistry.get_gauge("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46") - != ZERO_ADDRESS + gauge = metaregistry.get_gauge( + "0xD51a44d3FaE010294C616388b506AcdA1bfAAE46" ) + assert gauge != ZERO_ADDRESS if __name__ == "__main__": - main() + main(setup()) diff --git a/scripts/class_diagram.py b/scripts/class_diagram.py new file mode 100644 index 0000000..19ea47a --- /dev/null +++ b/scripts/class_diagram.py @@ -0,0 +1,119 @@ +""" +Generates a mermaid class diagram with the contract selectors and + the dependencies between contracts. +Usage: + python scripts/class_diagram.py [glob_pattern] (--internal) (--members) +""" +import sys +from base64 import b64encode +from dataclasses import dataclass +from glob import glob +from pathlib import Path + +import boa +from vyper import ast + + +@dataclass +class ContractDependencies: + """ + Describes the information extracted from each vyper contract in order to generate + the class diagram. + """ + + # the name of the contract (file) + contract: str + # a dict of the interface name to a list of the selectors where it's used + interfaces: dict[str, set[str]] + functions: list[str] + variables: list[str] + + +def get_function_descriptor(node: ast.FunctionDef) -> str: + """Returns a string describing the function signature.""" + args_descriptor = ", ".join(a.node_source_code for a in node.args.args) + return f"({node.returns} {node.name} {args_descriptor})" + + +def parse_contract(filename: str) -> ContractDependencies: + """ + The interfaces used in a contract and a list of the slots where it's used. + :param filename: The path to the Vyper source code. + :return: a list of the interface names used in the contract + """ + module = boa.load_partial(filename).compiler_data.vyper_module + interfaces = module.get_children(ast.InterfaceDef) + return ContractDependencies( + contract=Path(filename).stem, + interfaces={ + i.name: { + node.get_ancestor(ast.FunctionDef).name + for node in module.get_descendants(ast.Call) + if getattr(node.func, "id", None) == i.name + } + for i in interfaces + }, + functions=sorted( + { + get_function_descriptor(node) + for node in module.get_children(ast.FunctionDef) + } + ), + variables=sorted( + { + node.node_source_code.replace("(", " ").replace(")", "") + for node in module.get_children(ast.VariableDecl) + } + ), + ) + + +def main(pattern: str, internal=True, members=True) -> None: + """ + Generates a mermaid graph of the dependencies between contracts. + Prints the graph and a link to the image via the Mermaid Live Editor. + :param pattern: a glob pattern to match the contracts + :param internal: whether to include internal dependencies + :param members: whether to include the members of each contract + """ + contracts = [ + parse_contract(filename) for filename in glob(pattern, recursive=True) + ] + names = {c.contract for c in contracts} + + classes = ( + [ + line + for contract in contracts + for line in [f" class {contract.contract} {{"] + + contract.variables + + contract.functions + + [" }"] + ] + if members + else [] + ) + + connections = [ + f" {contract.contract} --> {interface}: " + f"{', '.join(selectors) if 0 < len(selectors) < 3 else f'{len(selectors)} selectors'}" + for contract in contracts + for interface, selectors in contract.interfaces.items() + if not internal or interface in names + ] + + graph = "\n".join(["classDiagram"] + classes + connections) + + print(graph) + print( + f"https://mermaid.ink/img/{b64encode(graph.encode()).decode('ascii')}" + ) + + +if __name__ == "__main__": + args = sys.argv[1:] + main( + pattern=args[0] if args else "contracts/**/*.vy", + internal="--internal" in args, + members="--members" in args, + ) diff --git a/scripts/deploy.py b/scripts/deploy.py index 51cfc6e..18d6dc6 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -1,83 +1,110 @@ -import click -from ape import project -from ape.cli import NetworkBoundCommand, account_option, network_option +""" +Deploy the contracts to the network. +Usage for fork mode: + scripts/deploy.py + requires the RPC_ETHEREUM environment variable to be set +Usage for prod mode: + scripts/deploy.py --prod + requires the URL and ACCOUNT environment variables to be set +""" +import boa from eth_abi import encode +from rich import Console as RichConsole + +from scripts.deployment_utils import setup_environment ADDRESS_PROVIDER = "0x0000000022D53366457F9d5E68Ec105046FC4383" STABLE_REGISTRY_ADDRESS = "0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5" STABLE_FACTORY_ADDRESS = "0xB9fC157394Af804a3578134A6585C0dc9cc990d4" -CRYPTO_REGISTRY_ADDRESS = "" # left blank because a new one gets deployed CRYPTO_FACTORY_ADDRESS = "0xF18056Bbd320E96A48e3Fbf8bC061322531aac99" -@click.group(short_help="Deploy the project") -def cli(): - pass - +def main(): + """ + Deploy the contracts to the network. + It does the following: + 1. deploys the base pool registry + 2. deploys the crypto registry + 3. deploys the stable registry handler + 4. deploys the stable factory handler + 5. deploys the crypto registry handler + 6. deploys the crypto factory handler + 7. deploys the metaregistry + """ + console = RichConsole() + setup_environment(console) -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -def main(network, account): # deploy basepool registry: - base_pool_registry = account.deploy(project.BasePoolRegistry) + base_pool_registry = boa.load( + "contracts/mainnet/registries/BasePoolRegistry.vy" + ) # deploy crypto registry: - print( + console.log( "Crypto Registry constructor arguments: ", encode(["address", "address"], [ADDRESS_PROVIDER, base_pool_registry]), ) - crypto_registry = account.deploy( - project.CryptoRegistryV1, + crypto_registry = boa.load( + "contracts/mainnet/registries/CryptoRegistryV1.vy", ADDRESS_PROVIDER, base_pool_registry, ) # deploy stable registry handler: - print( + console.log( "Stable Registry Handler constructor arguments: ", encode(["address"], [STABLE_REGISTRY_ADDRESS]).hex(), ) - account.deploy(project.StableRegistryHandler, STABLE_REGISTRY_ADDRESS) + boa.load( + "contracts/mainnet/registry_handlers/StableRegistryHandler.vy", + STABLE_REGISTRY_ADDRESS, + ) # deploy stable factory handler: - print( + console.log( "Stable Factory Handler constructor arguments: ", encode( ["address", "address"], [STABLE_FACTORY_ADDRESS, base_pool_registry], ).hex(), ) - account.deploy( - project.StableFactoryHandler, + boa.load( + "contracts/mainnet/registry_handlers/StableFactoryHandler.vy", STABLE_FACTORY_ADDRESS, base_pool_registry, ) # deploy crypto registry handler: - print( + console.log( "Crypto Registry Handler constructor arguments: ", encode(["address"], [crypto_registry]).hex(), ) - account.deploy(project.CryptoRegistryHandler, crypto_registry) + boa.load( + "contracts/mainnet/registry_handlers/CryptoRegistryHandler.vy", + crypto_registry, + ) # deploy crypto factory handler: - print( + console.log( "Crypto Factory Handler constructor arguments: ", encode( ["address", "address"], [CRYPTO_FACTORY_ADDRESS, base_pool_registry], ).hex(), ) - account.deploy( - project.CryptoFactoryHandler, + boa.load( + "contracts/mainnet/registry_handlers/CryptoFactoryHandler.vy", CRYPTO_FACTORY_ADDRESS, base_pool_registry, ) # deploy metaregistry: - print( + console.log( "MetaRegistry constructor arguments: ", encode(["address"], [ADDRESS_PROVIDER]).hex(), ) - account.deploy(project.MetaRegistry, ADDRESS_PROVIDER) + boa.load("contracts/mainnet/MetaRegistry.vy", ADDRESS_PROVIDER) + + +if __name__ == "__main__": + main() diff --git a/scripts/deploy_crypto_registry_handler.py b/scripts/deploy_crypto_registry_handler.py index 0c25040..5a9cc3c 100644 --- a/scripts/deploy_crypto_registry_handler.py +++ b/scripts/deploy_crypto_registry_handler.py @@ -1,22 +1,28 @@ -import click -from ape import project -from ape.cli import NetworkBoundCommand, account_option, network_option -from eth_abi import encode +""" +Deploy the Crypto Registry Handler contract +Requires the Crypto Registry contract to be deployed first. + +Usage for fork mode: + scripts/deploy_crypto_registry_handler.py + requires the RPC_ETHEREUM environment variable to be set +Usage for prod mode: + scripts/deploy_crypto_registry_handler.py --prod + requires the URL and ACCOUNT environment variables to be set +""" +import boa +from rich import Console as RichConsole + +from scripts.deployment_utils import setup_environment CRYPTO_REGISTRY_ADDRESS = "0x9a32aF1A11D9c937aEa61A3790C2983257eA8Bc0" -@click.group(short_help="Deploy the registry handler") -def cli(): - pass +def main(): + console = RichConsole() + console.log("Deploying Crypto Registry Handler contract...") + setup_environment(console) + boa.load("contracts/mainnet/registry_handlers/CryptoRegistryHandler.vy") -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -def main(network, account): - print( - "Crypto Registry Handler constructor arguments: ", - encode(["address"], [CRYPTO_REGISTRY_ADDRESS]).hex(), - ) - account.deploy(project.CryptoRegistryHandler, CRYPTO_REGISTRY_ADDRESS) +if __name__ == "__main__": + main() diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py new file mode 100644 index 0000000..969b01b --- /dev/null +++ b/scripts/deployment_utils.py @@ -0,0 +1,34 @@ +import sys +from os import environ + +import boa +from boa.network import NetworkEnv +from eth_account import Account +from rich.console import Console as RichConsole + +FIDDYDEPLOYER = "0x2d12D0907A388811e3AA855A550F959501d303EE" + + +def setup_environment(console: RichConsole): + """ + Sets up the environment for deployment scripts. + Requires the following environment variables: + For forks (default): + - RPC_ETHEREUM: URL to fork from. + For prod (script called with --prod argument): + - URL: URL to connect to. + - ACCOUNT: Private key of account to use. + + :param console: RichConsole instance to log to. + :return: Whether the environment is in prod mode. + """ + if "--prod" in sys.argv: + console.log("Running script in prod mode...") + boa.set_env(NetworkEnv(environ["URL"])) + boa.env.add_account(Account.from_key(environ["ACCOUNT"])) + return True + + console.log("Simulation Mode. Writing to mainnet-fork.") + boa.env.fork(url=environ["RPC_ETHEREUM"]) + boa.env.eoa = FIDDYDEPLOYER + return False diff --git a/scripts/print_missing.py b/scripts/print_missing.py index 8a4aa9e..21dab1c 100644 --- a/scripts/print_missing.py +++ b/scripts/print_missing.py @@ -3,11 +3,10 @@ import boa from rich.console import Console from rich.table import Table +from sortedcontainers import SortedDict from vyper.compiler.output import build_abi_output -MISSING = "[red]✖[/red]" -PRESENT = "[green]✓[/green]" -META_NAME = "MetaRegistry" +CELL_VALUES = {True: "[green]✓[/green]", False: "[red]✖[/red]"} def get_view_functions(abi: list[dict]) -> set[str]: @@ -18,56 +17,67 @@ def get_view_functions(abi: list[dict]) -> set[str]: return {d["name"] for d in abi if d.get("stateMutability") == "view"} -def main() -> None: +def get_functions_from_abi_json(contract_name: str) -> set[str]: """ - Calculate the missing functions from the registry and print them in a table. + Get the view functions from an ABI JSON file. + :param contract_name: the name of the contract + :return: the view functions from the ABI JSON file """ - console = Console() - metaregistry = boa.load_partial(f"contracts/mainnet/{META_NAME}.vy") + with open(f"contracts/interfaces/{contract_name}.json") as f: + abi = json.load(f) + return get_view_functions(abi) + + +def get_functions_from_source(name: str) -> set[str]: + """ + Get the view functions from a Vyper source file. + :param name: the name of the source file + :return: the view functions from the source file + """ + deployer = boa.load_partial(f"contracts/mainnet/{name}.vy") meta_functions = get_view_functions( - abi=build_abi_output(metaregistry.compiler_data) + abi=build_abi_output(deployer.compiler_data) ) - registry_names = [ - "CryptoFactory", - "CryptoRegistry", - "StableFactory", - "StableRegistry", - ] + return meta_functions - registry_functions = {META_NAME: meta_functions} - for registry_name in registry_names: - with open(f"contracts/interfaces/{registry_name}.json") as f: - registry_abi = json.load(f) - registry_functions[registry_name] = get_view_functions(registry_abi) - table = create_table(registry_functions) - return console.print(table) +def main() -> None: + """ + Calculate the missing functions from the registry and print them in a table. + """ + registry_functions = SortedDict( + [ + ("MetaRegistry", get_functions_from_source("MetaRegistry")), + ("CryptoFactory", get_functions_from_abi_json("CryptoFactory")), + ("CryptoRegistry", get_functions_from_abi_json("CryptoRegistry")), + ("StableFactory", get_functions_from_abi_json("StableFactory")), + ("StableRegistry", get_functions_from_abi_json("StableRegistry")), + ] + ) + table = compare_contracts(registry_functions) + return Console().print(table) -def create_table(registry_functions: dict[str, set[str]]) -> Table: +def compare_contracts(contract_functions: dict[str, set[str]]) -> Table: """ Create a table with the missing functions. - :param registry_functions: the functions from the registries + :param contract_functions: A dictionary with the contract name as key and a set of the + view functions as value :return: the table with the missing functions """ - table = Table(title="Missing Functions") - all_functions = set.union(*registry_functions.values()) - registries = [META_NAME] + sorted( - registry_name - for registry_name in registry_functions - if registry_name != META_NAME + table = Table( + "Function Name", + *contract_functions, + title="Comparison between the contract view functions", ) - table.add_column("Function Name") - for registry_name in registries: - table.add_column(registry_name) - for function_name in sorted(all_functions): - cells = [ - PRESENT - if function_name in registry_functions[registry_name] - else MISSING - for registry_name in registries - ] - table.add_row(function_name, *cells) + for function_name in sorted(set.union(*contract_functions.values())): + table.add_row( + function_name, + *[ + CELL_VALUES[function_name in functions] + for functions in contract_functions.values() + ], + ) return table diff --git a/scripts/setup_metaregistry.py b/scripts/setup_metaregistry.py index b0f8a6d..5fad82d 100644 --- a/scripts/setup_metaregistry.py +++ b/scripts/setup_metaregistry.py @@ -1,8 +1,19 @@ +""" +Sets up the metaregistry. + +Usage for fork mode: + scripts/setup_metaregistry.py + requires the RPC_ETHEREUM environment variable to be set +Usage for prod mode: + scripts/setup_metaregistry.py --prod + requires the URL and ACCOUNT environment variables to be set +""" import sys -import click +import boa from rich.console import Console as RichConsole +from scripts.deployment_utils import setup_environment from tests.utils import ZERO_ADDRESS, get_deployed_contract RICH_CONSOLE = RichConsole(file=sys.stdout) @@ -128,12 +139,18 @@ } -@click.group(short_help="Deploy the project") -def cli(): - pass +def main(): + """ + This script sets up the metaregistry. It does the following: + 1. Adds base pools to base pool registry. + 2. Adds crypto pools to crypto registry. + 3. Adds registry handlers to metaregistry. + 4. Adds metaregistry to address provider. + """ + setup_environment(RICH_CONSOLE) + account = boa.env.eoa -def main(network: str, account: str): # admin only: only admin of ADDRESSPROVIDER's proxy admin can do the following: address_provider = get_deployed_contract( "AddressProvider", ADDRESS_PROVIDER @@ -141,10 +158,6 @@ def main(network: str, account: str): address_provider_admin = address_provider.admin() proxy_admin = get_deployed_contract("ProxyAdmin", address_provider_admin) - if network == "ethereum:mainnet-fork": - RICH_CONSOLE.log("Simulation mode.") - account = proxy_admin.admins(1) - # deployed contracts: base_pool_registry = get_deployed_contract( "BasePoolRegistry", "0xDE3eAD9B2145bBA2EB74007e58ED07308716B725" @@ -323,3 +336,7 @@ def main(network: str, account: str): "0x853d955aCEf822Db058eb8505911ED77F175b99e", ) ) + + +if __name__ == "__main__": + main() diff --git a/tests/conftest.py b/tests/conftest.py index afc28ca..26565f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import logging from os import environ import boa @@ -27,20 +28,28 @@ def pytest_sessionstart(): global STABLE_REGISTRY_POOLS global ALL_POOLS + logging.info("Connecting to Ethereum fork") # connect to the network. TODO: use Drpc-Key header instead of GET param boa.env.fork(url=environ["RPC_ETHEREUM"]) # store registries globally, so we don't have to recreate multiple times when generating tests. # TODO: Can we move these to fixtures? + logging.info("Retrieving stable registry pools") STABLE_REGISTRY_POOLS = get_contract_pools( "StableRegistry", "0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5" ) + + logging.info("Retrieving stable factory pools") STABLE_FACTORY_POOLS = get_contract_pools( "StableFactory", "0xB9fC157394Af804a3578134A6585C0dc9cc990d4" ) + + logging.info("Retrieving crypto registry pools") CRYPTO_REGISTRY_POOLS = get_contract_pools( "CryptoRegistry", "0x8F942C20D02bEfc377D41445793068908E2250D0" ) + + logging.info("Retrieving crypto factory pools") CRYPTO_FACTORY_POOLS = get_contract_pools( "CryptoFactory", "0xF18056Bbd320E96A48e3Fbf8bC061322531aac99" ) 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 9f9d004..fce1773 100644 --- a/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py +++ b/tests/mainnet/metaregistry/api/test_find_pool_for_coins.py @@ -1,7 +1,5 @@ import itertools -import pytest - from tests.utils import ZERO_ADDRESS # NOTE: This is the most important method in the metaregistry contract since it will be used @@ -31,7 +29,6 @@ def _get_all_combinations(metaregistry, pool): return all_combinations -@pytest.mark.skip() # TODO: This test is spawning a lot of test cases and takes >1:30h to run def test_all(populated_metaregistry, pool): combinations = _get_all_combinations(populated_metaregistry, pool) for combination in combinations: diff --git a/tests/mainnet/metaregistry/api/test_get_admin_balances.py b/tests/mainnet/metaregistry/api/test_get_admin_balances.py index 65cbb28..32f4122 100644 --- a/tests/mainnet/metaregistry/api/test_get_admin_balances.py +++ b/tests/mainnet/metaregistry/api/test_get_admin_balances.py @@ -1,26 +1,59 @@ import boa import pytest from boa import BoaError -from eth.codecs.abi.exceptions import DecodeError +from eth.codecs.abi.exceptions import DecodeError as ABIDecodeError -from tests.utils import get_deployed_token_contract +from tests.utils import ZERO_ADDRESS, get_deployed_token_contract def pre_test_checks(metaregistry, pool): - if sum(metaregistry.get_balances(pool)) == 0: - pytest.skip("empty pool: skipping") + """ + Checks whether the pool is in a state that allows the test to run. + Otherwise, skips the test. + + The checks are: + - the pool has a balance + - the LP token has a supply + - the coin balances are not lower than the admin balances + """ + try: + if sum(metaregistry.get_balances(pool)) == 0: + return pytest.skip(f"Pool {pool} has no balance") + 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) try: - contract = get_deployed_token_contract(metaregistry.get_lp_token(pool)) + contract = get_deployed_token_contract(lp_token) if contract.totalSupply() == 0: - pytest.skip("LP token supply is zero") - except (BoaError, DecodeError) as err: # TODO: Document why this happens - pytest.skip( - f"{type(err).__name__} for token {metaregistry.get_lp_token(pool)}: " - f"Skipping because of {err.msg}" + 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 + 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/utils.py b/tests/utils.py index e888a45..57ced7c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -55,18 +55,18 @@ def get_deployed_token_contract(address: str) -> VyperContract: abi = json.dumps( [ { + "name": "name", "constant": True, "inputs": [], - "name": "name", "outputs": [{"name": "", "type": "string"}], "payable": False, "stateMutability": "view", "type": "function", }, { + "name": "decimals", "constant": True, "inputs": [], - "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", @@ -74,14 +74,14 @@ def get_deployed_token_contract(address: str) -> VyperContract: }, { "name": "totalSupply", - "constant": True, "inputs": [], - "outputs": [{"name": "", "type": "uint256"}], - "payable": False, + "outputs": [{"type": "uint256", "name": ""}], "stateMutability": "view", "type": "function", + "gas": 2531, }, { + "name": "balanceOf", "inputs": [ { "internalType": "address", @@ -89,7 +89,6 @@ def get_deployed_token_contract(address: str) -> VyperContract: "type": "address", } ], - "name": "balanceOf", "outputs": [ {"internalType": "uint256", "name": "", "type": "uint256"} ],