Skip to content

Commit

Permalink
fix[tool]: fix output formats for .vyz files (vyperlang#4338)
Browse files Browse the repository at this point in the history
run the format name through the translate map. for instance,
`annotated_ast` output format was not working for `.vyz` files.

this commit has some additional fixes that were discovered when adding
the integration tests and refactoring related to the settings not
getting propagated uniformly across different entry points.

---------

Co-authored-by: cyberthirst <cyberthirst.eth@gmail.com>
  • Loading branch information
charles-cooper and cyberthirst authored Dec 29, 2024
1 parent 614ea0d commit 194d60a
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 15 deletions.
16 changes: 14 additions & 2 deletions tests/functional/venom/test_venom_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from tests.venom_utils import assert_ctx_eq, parse_venom
from vyper.compiler import compile_code
from vyper.compiler.phases import generate_bytecode
from vyper.compiler.settings import OptimizationLevel
from vyper.venom import generate_assembly_experimental, run_passes_on
from vyper.venom.context import IRContext

Expand All @@ -20,14 +21,19 @@ def get_example_vy_filenames():


@pytest.mark.parametrize("vy_filename", get_example_vy_filenames())
def test_round_trip_examples(vy_filename, optimize, compiler_settings):
def test_round_trip_examples(vy_filename, debug, optimize, compiler_settings, request):
"""
Check all examples round trip
"""
path = f"examples/{vy_filename}"
with open(path) as f:
vyper_source = f.read()

if debug and optimize == OptimizationLevel.CODESIZE:
# FIXME: some round-trips fail when debug is enabled due to labels
# not getting pinned
request.node.add_marker(pytest.mark.xfail(strict=False))

_round_trip_helper(vyper_source, optimize, compiler_settings)


Expand All @@ -45,11 +51,17 @@ def _loop() -> uint256:


@pytest.mark.parametrize("vyper_source", vyper_sources)
def test_round_trip_sources(vyper_source, optimize, compiler_settings):
def test_round_trip_sources(vyper_source, debug, optimize, compiler_settings, request):
"""
Test vyper_sources round trip
"""
vyper_source = textwrap.dedent(vyper_source)

if debug and optimize == OptimizationLevel.CODESIZE:
# FIXME: some round-trips fail when debug is enabled due to labels
# not getting pinned
request.node.add_marker(pytest.mark.xfail(strict=False))

_round_trip_helper(vyper_source, optimize, compiler_settings)


Expand Down
99 changes: 99 additions & 0 deletions tests/unit/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,105 @@ def test_archive_b64_output(input_files):
assert out[contract_file] == out2[archive_path]


def test_archive_compile_options(input_files):
tmpdir, _, _, contract_file = input_files
search_paths = [".", tmpdir]

options = ["abi_python", "json", "ast", "annotated_ast", "ir_json"]

for option in options:
out = compile_files([contract_file], ["archive_b64", option], paths=search_paths)

archive_b64 = out[contract_file].pop("archive_b64")

archive_path = Path("foo.zip.b64")
with archive_path.open("w") as f:
f.write(archive_b64)

# compare compiling the two input bundles
out2 = compile_files([archive_path], [option])

if option in ["ast", "annotated_ast"]:
# would have to normalize paths and imports, so just verify it compiles
continue

assert out[contract_file] == out2[archive_path]


format_options = [
"bytecode",
"bytecode_runtime",
"blueprint_bytecode",
"abi",
"abi_python",
"source_map",
"source_map_runtime",
"method_identifiers",
"userdoc",
"devdoc",
"metadata",
"combined_json",
"layout",
"ast",
"annotated_ast",
"interface",
"external_interface",
"opcodes",
"opcodes_runtime",
"ir",
"ir_json",
"ir_runtime",
"asm",
"integrity",
"archive",
"solc_json",
]


def test_compile_vyz_with_options(input_files):
tmpdir, _, _, contract_file = input_files
search_paths = [".", tmpdir]

for option in format_options:
out_archive = compile_files([contract_file], ["archive"], paths=search_paths)

archive = out_archive[contract_file].pop("archive")

archive_path = Path("foo.zip.out.vyz")
with archive_path.open("wb") as f:
f.write(archive)

# compare compiling the two input bundles
out = compile_files([contract_file], [option], paths=search_paths)
out2 = compile_files([archive_path], [option])

if option in ["ast", "annotated_ast", "metadata"]:
# would have to normalize paths and imports, so just verify it compiles
continue

if option in ["ir_runtime", "ir", "archive"]:
# ir+ir_runtime is different due to being different compiler runs
# archive is different due to different metadata (timestamps)
continue

assert out[contract_file] == out2[archive_path]


def test_archive_compile_simultaneous_options(input_files):
tmpdir, _, _, contract_file = input_files
search_paths = [".", tmpdir]

for option in format_options:
with pytest.raises(ValueError) as e:
_ = compile_files([contract_file], ["archive", option], paths=search_paths)

err_opt = "archive"
if option in ("combined_json", "solc_json"):
err_opt = option

assert f"If using {err_opt} it must be the only output format requested" in str(e.value)


def test_solc_json_output(input_files):
tmpdir, _, _, contract_file = input_files
search_paths = [".", tmpdir]
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/cli/vyper_json/test_compile_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def oopsie(a: uint256) -> bool:


@pytest.fixture(scope="function")
def input_json(optimize, evm_version, experimental_codegen):
def input_json(optimize, evm_version, experimental_codegen, debug):
return {
"language": "Vyper",
"sources": {
Expand All @@ -87,6 +87,7 @@ def input_json(optimize, evm_version, experimental_codegen):
"optimize": optimize.name.lower(),
"evmVersion": evm_version,
"experimentalCodegen": experimental_codegen,
"debug": debug,
},
}

Expand Down
20 changes: 16 additions & 4 deletions tests/unit/compiler/test_bytecode_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_bytecode_runtime():
assert out["bytecode_runtime"].removeprefix("0x") in out["bytecode"].removeprefix("0x")


def test_bytecode_signature():
def test_bytecode_signature(optimize, debug):
out = vyper.compile_code(
simple_contract_code, output_formats=["bytecode_runtime", "bytecode", "integrity"]
)
Expand All @@ -65,10 +65,16 @@ def test_bytecode_signature():
metadata = _parse_cbor_metadata(initcode)
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

if debug and optimize == OptimizationLevel.CODESIZE:
# debug forces dense jumptable no matter the size of selector table
expected_data_section_lengths = [5, 7]
else:
expected_data_section_lengths = []

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == []
assert data_section_lengths == expected_data_section_lengths
assert immutables_len == 0
assert compiler == {"vyper": list(vyper.version.version_tuple)}

Expand Down Expand Up @@ -119,7 +125,7 @@ def test_bytecode_signature_sparse_jumptable():
assert compiler == {"vyper": list(vyper.version.version_tuple)}


def test_bytecode_signature_immutables():
def test_bytecode_signature_immutables(debug, optimize):
out = vyper.compile_code(
has_immutables, output_formats=["bytecode_runtime", "bytecode", "integrity"]
)
Expand All @@ -130,10 +136,16 @@ def test_bytecode_signature_immutables():
metadata = _parse_cbor_metadata(initcode)
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

if debug and optimize == OptimizationLevel.CODESIZE:
# debug forces dense jumptable no matter the size of selector table
expected_data_section_lengths = [5, 7]
else:
expected_data_section_lengths = []

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == []
assert data_section_lengths == expected_data_section_lengths
assert immutables_len == 32
assert compiler == {"vyper": list(vyper.version.version_tuple)}

Expand Down
2 changes: 1 addition & 1 deletion vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ def compile_files(
# we allow this instead of requiring a different mode (like
# `--zip`) so that verifier pipelines do not need a different
# workflow for archive files and single-file contracts.
output = compile_from_zip(file_name, output_formats, settings, no_bytecode_metadata)
output = compile_from_zip(file_name, final_formats, settings, no_bytecode_metadata)
ret[file_path] = output
continue
except NotZipInput:
Expand Down
11 changes: 10 additions & 1 deletion vyper/cli/vyper_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,17 @@ def get_settings(input_dict: dict) -> Settings:
else:
assert optimize is None

debug = input_dict["settings"].get("debug", None)

# TODO: maybe change these to camelCase for consistency
enable_decimals = input_dict["settings"].get("enable_decimals", None)

return Settings(
evm_version=evm_version, optimize=optimize, experimental_codegen=experimental_codegen
evm_version=evm_version,
optimize=optimize,
experimental_codegen=experimental_codegen,
debug=debug,
enable_decimals=enable_decimals,
)


Expand Down
2 changes: 2 additions & 0 deletions vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle, InputBundle
from vyper.compiler.settings import OptimizationLevel, Settings, anchor_settings, merge_settings
from vyper.ir import compile_ir, optimizer
from vyper.ir.compile_ir import reset_symbols
from vyper.semantics import analyze_module, set_data_positions, validate_compilation_target
from vyper.semantics.analysis.data_positions import generate_layout_export
from vyper.semantics.analysis.imports import resolve_imports
Expand Down Expand Up @@ -310,6 +311,7 @@ def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode,
"""
# make IR output the same between runs
codegen.reset_names()
reset_symbols()

with anchor_settings(settings):
ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx)
Expand Down
12 changes: 6 additions & 6 deletions vyper/compiler/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ def _merge_one(lhs, rhs, helpstr):
return lhs if rhs is None else rhs

ret = Settings()
ret.evm_version = _merge_one(one.evm_version, two.evm_version, "evm version")
ret.optimize = _merge_one(one.optimize, two.optimize, "optimize")
ret.experimental_codegen = _merge_one(
one.experimental_codegen, two.experimental_codegen, "experimental codegen"
)
ret.enable_decimals = _merge_one(one.enable_decimals, two.enable_decimals, "enable-decimals")
for field in dataclasses.fields(ret):
if field.name == "compiler_version":
continue
pretty_name = field.name.replace("_", "-") # e.g. evm_version -> evm-version
val = _merge_one(getattr(one, field.name), getattr(two, field.name), pretty_name)
setattr(ret, field.name, val)

return ret

Expand Down
5 changes: 5 additions & 0 deletions vyper/ir/compile_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def mksymbol(name=""):
return f"_sym_{name}{_next_symbol}"


def reset_symbols():
global _next_symbol
_next_symbol = 0


def mkdebug(pc_debugger, ast_source):
i = Instruction("DEBUG", ast_source)
i.pc_debugger = pc_debugger
Expand Down

0 comments on commit 194d60a

Please sign in to comment.