Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added cli for inspecting vyper contracts #53

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions moccasin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,10 @@ def generate_main_parser_and_sub_parsers() -> (
import_parser.add_argument("name", help="Name of account to import")

# Inspect Json
inspect_parser = wallet_subparsers.add_parser(
"inspect", help="View the JSON of a keystore file"
view_parser = wallet_subparsers.add_parser(
"view", help="View the JSON of a keystore file"
)
inspect_parser.add_argument("keystore_file_name", help="Name of keystore file")
view_parser.add_argument("keystore_file_name", help="Name of keystore file")

# Decrypt Keystore
decrypt_keystore_parser = wallet_subparsers.add_parser(
Expand Down Expand Up @@ -512,6 +512,49 @@ def generate_main_parser_and_sub_parsers() -> (
"--json", help="Format as json.", action="store_true"
)

# ------------------------------------------------------------------
# INSPECT COMMAND
# ------------------------------------------------------------------
inspect_parser = sub_parsers.add_parser(
"inspect",
help="Inspect compiler data of a contract.",
description="""Inspect compiler data of a contract.

This command will directly use the Vyper compiler to access this data. For example, to get function signatures and selectors run:

mox inspect src/Counter.vy methods

or

mox inspect Counter methods""",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
inspect_parser.add_argument(
"contract",
help="Path or Contract name of the contract you want to inspect.",
type=str,
)
inspect_parser.add_argument(
"inspect_type",
help="Type of inspection you want to do.",
choices=[
"methods",
"abi",
"natspec",
"storage-layout",
"ir-nodes",
"ir-runtime",
"function-signatures",
"function-selectors",
"selectors",
"signatures",
"assembly",
"venom-functions",
"bytecode",
"bytecode-runtime",
],
)

return main_parser, sub_parsers


Expand Down
78 changes: 78 additions & 0 deletions moccasin/commands/inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from argparse import Namespace
from pathlib import Path
from typing import Any

from pytest import Config
from moccasin.config import get_config
from moccasin.commands.compile import compile_
import pprint

FUNCTION_SIGNATURES_ALTS = [
"methods",
"signatures",
"selectors",
"function-selectors",
"function_signatures",
]


def main(args: Namespace) -> int:
inspect_type = args.inspect_type.replace("-", "_")
inspect_contract(args.contract, inspect_type, print_out=True)
return 0


def inspect_contract(contract: str, inspect_type: str, print_out: bool = False) -> Any:
config = get_config()

contract_path = _find_contract(contract, config)
vyper_deployer = compile_(contract_path, config.build_folder)

if inspect_type in FUNCTION_SIGNATURES_ALTS:
inspect_type = "function_signatures"

inspected_data = getattr(vyper_deployer.compiler_data, inspect_type)
final_data = inspected_data

if inspect_type == "function_signatures":
final_data = {}
for _, contract_function in inspected_data.items():
for signature in contract_function.method_ids:
int_selector = contract_function.method_ids[signature]
final_data[signature] = f"{hex(int_selector)} ({int_selector})"

if print_out:
print(f"Signatures and selectors for {contract_path.stem}:")
if inspect_type == "function_signatures":
for function, selector in final_data.items():
pprint.pprint(f"{function}: {selector}")
else:
pprint.pprint(final_data)
return final_data


def _find_contract(contract_or_contract_path: str, config: Config) -> Path:
config_root = config.get_root()
if contract_or_contract_path.endswith(".vy"):
contract_name = config_root / contract_or_contract_path
else:
contract_name = contract_or_contract_path + ".vy"

contracts_location = config_root / config.contracts_folder
contract_paths = list(contracts_location.rglob(contract_name))

contract_path = None
if not contract_paths:
raise FileNotFoundError(
f"Contract file '{contract_name}' not found under '{config_root}'."
)
elif len(contract_paths) > 1:
found_paths = "\n".join(str(path) for path in contract_paths)
raise FileExistsError(
f"Multiple contract files named '{contract_name}' found:\n{found_paths}\n"
"Please specify the path to the contract file."
)
else:
# Exactly one file found
contract_path = contract_paths[0]
return contract_path
6 changes: 3 additions & 3 deletions moccasin/commands/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def main(args: Namespace) -> int:
return import_private_key(args.name)
elif wallet_command == "delete":
return delete_keystore(args.keystore_file_name)
elif wallet_command == "inspect":
inspect(args.keystore_file_name)
elif wallet_command == "view":
view_wallet(args.keystore_file_name)
elif wallet_command == "decrypt":
key = decrypt_key(
args.keystore_file_name,
Expand All @@ -48,7 +48,7 @@ def main(args: Namespace) -> int:
return 0


def inspect(
def view_wallet(
keystore_file_name: str, keystores_path: Path = DEFAULT_KEYSTORES_PATH
) -> dict:
keystore_path = keystores_path.joinpath(keystore_file_name)
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_unit_inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from moccasin.commands.inspect import inspect_contract
from tests.conftest import COMPLEX_PROJECT_PATH
from pathlib import Path
import os


def test_inspect_counter(complex_project_config):
expected_dir = {
"set_number(uint256)": "0xd6d1ee14 (3604082196)",
"increment()": "0xd09de08a (3500007562)",
}
current_dir = Path.cwd()
try:
os.chdir(current_dir.joinpath(COMPLEX_PROJECT_PATH))
result = inspect_contract("Counter", "function_signatures", print_out=False)
finally:
os.chdir(current_dir)
assert result == expected_dir
File renamed without changes.
6 changes: 3 additions & 3 deletions tests/unit/test_unit_wallet.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from pathlib import Path
from typing import List
from moccasin.commands.wallet import inspect, list_accounts
from moccasin.commands.wallet import view_wallet, list_accounts
from tests.conftest import ANVIL1_KEYSTORE_NAME, ANVIL_KEYSTORE_SAVED


def test_inspect(anvil_keystore):
result = inspect(ANVIL1_KEYSTORE_NAME, keystores_path=anvil_keystore)
def test_view_wallet(anvil_keystore):
result = view_wallet(ANVIL1_KEYSTORE_NAME, keystores_path=anvil_keystore)
assert result["address"] == ANVIL_KEYSTORE_SAVED["address"]


Expand Down
Loading