From 4b84b26bcf3be011ff60dda93bc83a26eb0c9b53 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:44:17 +0800 Subject: [PATCH 01/38] handle storage layout for json --- vyper/cli/vyper_json.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 9fcdf27baf..397e911fe7 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -12,6 +12,7 @@ from vyper.compiler.settings import OptimizationLevel, Settings from vyper.evm.opcodes import EVM_VERSIONS from vyper.exceptions import JSONError +from vyper.typing import StorageLayout from vyper.utils import OrderedSet, keccak256 TRANSLATE_MAP = { @@ -206,6 +207,18 @@ def get_inputs(input_dict: dict) -> dict[PurePath, Any]: return ret +def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayout]: + storage_layout_overrides: dict[PurePath, list[str]] = {} + for path, value in input_dict.get("storage_layout_overrides", {}).items(): + if path not in input_dict["sources"]: + raise JSONError(f"unknown target for storage layout override: {path}") + + path = PurePath(path) + storage_layout_overrides[path] = value["content"] + + return storage_layout_overrides + + # get unique output formats for each contract, given the input_dict # NOTE: would maybe be nice to raise on duplicated output formats def get_output_formats(input_dict: dict) -> dict[PurePath, list[str]]: @@ -290,6 +303,7 @@ def compile_from_input_dict( integrity = input_dict.get("integrity") sources = get_inputs(input_dict) + storage_layout_overrides = get_storage_layout_overrides(input_dict) output_formats = get_output_formats(input_dict) compilation_targets = list(output_formats.keys()) search_paths = get_search_paths(input_dict) @@ -299,6 +313,7 @@ def compile_from_input_dict( res, warnings_dict = {}, {} warnings.simplefilter("always") for contract_path in compilation_targets: + storage_layout_override = storage_layout_overrides.get(PurePath(contract_path), None) with warnings.catch_warnings(record=True) as caught_warnings: try: # use load_file to get a unique source_id @@ -308,6 +323,7 @@ def compile_from_input_dict( file, input_bundle=input_bundle, output_formats=output_formats[contract_path], + storage_layout_override=storage_layout_override, integrity_sum=integrity, settings=settings, no_bytecode_metadata=no_bytecode_metadata, @@ -347,6 +363,9 @@ def format_to_output_dict(compiler_data: dict) -> dict: if key in data: output_contracts[key] = data[key] + if "layout" in data: + output_contracts["layout"] = json.dumps(data["layout"]) + if "method_identifiers" in data: output_contracts["evm"] = {"methodIdentifiers": data["method_identifiers"]} From da1c47691bc8dd11841ae8480f948c8d28df115e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:44:37 +0800 Subject: [PATCH 02/38] add json test --- .../test_storage_layout_overrides.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index f02a8471e2..b9875a10bf 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -2,6 +2,7 @@ import pytest +from vyper.cli.vyper_json import compile_json from vyper.compiler import compile_code from vyper.evm.opcodes import version_check from vyper.exceptions import StorageLayoutException @@ -26,6 +27,37 @@ def test_storage_layout_overrides(): assert out["layout"] == expected_output +def test_storage_layout_overrides_json(): + code = """ +a: uint256 +b: uint256""" + + storage_layout_overrides = { + "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "b": {"type": "uint256", "slot": 0, "n_slots": 1}, + } + + input_json = { + "language": "Vyper", + "sources": { + "contracts/foo.vy": {"content": code}, + }, + "storage_layout_overrides": { + "contracts/foo.vy": { + "content": storage_layout_overrides + }, + }, + "settings": { + "outputSelection": {"*": ["*"]}, + }, + } + + out = compile_code( + code, output_formats=["layout"], storage_layout_override=storage_layout_overrides + ) + compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == out + + def test_storage_layout_for_more_complex(): code = """ foo: HashMap[address, uint256] From 8154230976dac04ddbc2a06713a1da7b0f5874cc Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:28:48 +0800 Subject: [PATCH 03/38] handle json round trip --- vyper/cli/vyper_json.py | 7 ++++--- vyper/compiler/output_bundle.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 397e911fe7..384ef6b1f7 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -208,13 +208,14 @@ def get_inputs(input_dict: dict) -> dict[PurePath, Any]: def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayout]: - storage_layout_overrides: dict[PurePath, list[str]] = {} + storage_layout_overrides: dict[PurePath, StorageLayout] = {} + for path, value in input_dict.get("storage_layout_overrides", {}).items(): if path not in input_dict["sources"]: raise JSONError(f"unknown target for storage layout override: {path}") path = PurePath(path) - storage_layout_overrides[path] = value["content"] + storage_layout_overrides[path] = value["content"]["storage_layout"] return storage_layout_overrides @@ -313,7 +314,7 @@ def compile_from_input_dict( res, warnings_dict = {}, {} warnings.simplefilter("always") for contract_path in compilation_targets: - storage_layout_override = storage_layout_overrides.get(PurePath(contract_path), None) + storage_layout_override = storage_layout_overrides.get(contract_path, None) with warnings.catch_warnings(record=True) as caught_warnings: try: # use load_file to get a unique source_id diff --git a/vyper/compiler/output_bundle.py b/vyper/compiler/output_bundle.py index 24a0d070cc..153211f34f 100644 --- a/vyper/compiler/output_bundle.py +++ b/vyper/compiler/output_bundle.py @@ -12,6 +12,7 @@ from vyper.compiler.settings import Settings from vyper.exceptions import CompilerPanic from vyper.semantics.analysis.imports import _is_builtin +from vyper.typing import StorageLayout from vyper.utils import get_long_version, safe_relpath # data structures and routines for constructing "output bundles", @@ -134,6 +135,11 @@ def bundle(self): def write_sources(self, sources: dict[str, CompilerInput]): raise NotImplementedError(f"write_sources: {self.__class__}") + def write_storage_layout_overrides( + self, compilation_target_path: str, storage_layout_override: StorageLayout + ): + raise NotImplementedError(f"write_storage_layout_overrides: {self.__class__}") + def write_search_paths(self, search_paths: list[str]): raise NotImplementedError(f"write_search_paths: {self.__class__}") @@ -160,6 +166,10 @@ def write(self): self.write_settings(self.compiler_data.original_settings) self.write_integrity(self.compiler_data.resolved_imports.integrity_sum) self.write_sources(self.bundle.compiler_inputs) + if self.compiler_data.storage_layout_override: + self.write_storage_layout_overrides( + self.bundle.compilation_target_path, self.compiler_data.storage_layout_override + ) class SolcJSONWriter(OutputBundleWriter): @@ -175,6 +185,13 @@ def write_sources(self, sources: dict[str, CompilerInput]): self._output["sources"].update(out) + def write_storage_layout_overrides( + self, compilation_target_path: str, storage_layout_override: StorageLayout + ): + self._output["storage_layout_overrides"] = { + compilation_target_path: {"content": storage_layout_override} + } + def write_search_paths(self, search_paths: list[str]): self._output["settings"]["search_paths"] = search_paths From cdf62b7c771c9a732cd93da92adadfc80748f143 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:28:59 +0800 Subject: [PATCH 04/38] add json round trip test --- .../cli/vyper_compile/test_compile_files.py | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 3856aa3362..f54cc092e8 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -1,4 +1,5 @@ import contextlib +import json import sys import zipfile from pathlib import Path @@ -297,6 +298,9 @@ def foo() -> uint256: import lib import jsonabi +a: uint256 +b: uint256 + @external def foo() -> uint256: return lib.foo() @@ -305,25 +309,40 @@ def foo() -> uint256: def bar(x: uint256) -> uint256: return extcall jsonabi(msg.sender).test_json(x) """ + storage_layout_overrides = { + "storage_layout": { + "a": {"type": "uint256", "n_slots": 1, "slot": 0}, + "b": {"type": "uint256", "n_slots": 1, "slot": 1}, + } + } + tmpdir = tmp_path_factory.mktemp("fake-package") with open(tmpdir / "lib.vy", "w") as f: f.write(library_source) with open(tmpdir / "jsonabi.json", "w") as f: f.write(json_source) + with open(tmpdir / "layout.json", "w") as f: + f.write(json.dumps(storage_layout_overrides)) contract_file = make_file("contract.vy", contract_source) - return (tmpdir, tmpdir / "lib.vy", tmpdir / "jsonabi.json", contract_file) + return ( + tmpdir, + tmpdir / "lib.vy", + tmpdir / "jsonabi.json", + tmpdir / "layout.json", + contract_file, + ) def test_import_sys_path(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file = input_files with mock_sys_path(tmpdir): assert compile_files([contract_file], ["combined_json"]) is not None def test_archive_output(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file = input_files search_paths = [".", tmpdir] s = compile_files([contract_file], ["archive"], paths=search_paths) @@ -342,7 +361,7 @@ def test_archive_output(input_files): def test_archive_b64_output(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file = input_files search_paths = [".", tmpdir] out = compile_files( @@ -361,11 +380,16 @@ def test_archive_b64_output(input_files): def test_solc_json_output(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] + storage_layout_paths = [storage_layout_path] - out = compile_files([contract_file], ["solc_json"], paths=search_paths) - + out = compile_files( + [contract_file], + ["solc_json"], + paths=search_paths, + storage_layout_paths=storage_layout_paths, + ) json_input = out[contract_file]["solc_json"] # check that round-tripping solc_json thru standard json produces @@ -380,7 +404,7 @@ def test_solc_json_output(input_files): # maybe this belongs in tests/unit/compiler? def test_integrity_sum(input_files): - tmpdir, library_file, jsonabi_file, contract_file = input_files + tmpdir, library_file, jsonabi_file, _, contract_file = input_files search_paths = [".", tmpdir] out = compile_files([contract_file], ["integrity"], paths=search_paths) From df36dd55dc51e02c5d1016585a5a42bfe5c3fa09 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:29:13 +0800 Subject: [PATCH 05/38] fix layout override test --- .../test_storage_layout_overrides.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index b9875a10bf..876b9d26f8 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -1,3 +1,4 @@ +import json import re import pytest @@ -39,23 +40,19 @@ def test_storage_layout_overrides_json(): input_json = { "language": "Vyper", - "sources": { - "contracts/foo.vy": {"content": code}, - }, + "sources": {"contracts/foo.vy": {"content": code}}, "storage_layout_overrides": { - "contracts/foo.vy": { - "content": storage_layout_overrides - }, - }, - "settings": { - "outputSelection": {"*": ["*"]}, + "contracts/foo.vy": {"content": {"storage_layout": storage_layout_overrides}} }, + "settings": {"outputSelection": {"*": ["*"]}}, } out = compile_code( code, output_formats=["layout"], storage_layout_override=storage_layout_overrides ) - compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == out + assert compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == json.dumps( + out["layout"] + ) def test_storage_layout_for_more_complex(): From c893919d9a9877ff46865e551c2c6c7991d83a06 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 11:44:50 +0800 Subject: [PATCH 06/38] fix layout output via json --- vyper/cli/vyper_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 384ef6b1f7..001af6baff 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -364,7 +364,7 @@ def format_to_output_dict(compiler_data: dict) -> dict: if key in data: output_contracts[key] = data[key] - if "layout" in data: + if data.get("layout"): output_contracts["layout"] = json.dumps(data["layout"]) if "method_identifiers" in data: From 08f039670066d9577ad620ac809a061ea65bbffb Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:29:54 +0800 Subject: [PATCH 07/38] include transient storage overrides --- vyper/cli/vyper_json.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 001af6baff..c6010f7463 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -215,7 +215,12 @@ def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayo raise JSONError(f"unknown target for storage layout override: {path}") path = PurePath(path) - storage_layout_overrides[path] = value["content"]["storage_layout"] + storage_layout_overrides_for_path: StorageLayout = {} + for layout_type in ("storage_layout", "transient_storage_layout"): + if layout := value["content"].get(layout_type): + storage_layout_overrides_for_path.update(layout) + + storage_layout_overrides[path] = storage_layout_overrides_for_path return storage_layout_overrides From 1f24a5032d8d2bb19c6a3559f9ae3c1d156a4e57 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:58:46 +0800 Subject: [PATCH 08/38] handle layout override for zip --- vyper/cli/compile_archive.py | 3 +++ vyper/compiler/output_bundle.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/vyper/cli/compile_archive.py b/vyper/cli/compile_archive.py index c6d07de9f1..204443ed94 100644 --- a/vyper/cli/compile_archive.py +++ b/vyper/cli/compile_archive.py @@ -45,6 +45,8 @@ def compiler_data_from_zip(file_name, settings, no_bytecode_metadata): fcontents = archive.read("MANIFEST/compilation_targets").decode("utf-8") compilation_targets = fcontents.splitlines() + storage_layout = json.loads(archive.read("MANIFEST/storage_layout.json").decode("utf-8")) + if len(compilation_targets) != 1: raise BadArchive("Multiple compilation targets not supported!") @@ -68,6 +70,7 @@ def compiler_data_from_zip(file_name, settings, no_bytecode_metadata): return CompilerData( file, input_bundle=input_bundle, + storage_layout=storage_layout, integrity_sum=integrity, settings=settings, no_bytecode_metadata=no_bytecode_metadata, diff --git a/vyper/compiler/output_bundle.py b/vyper/compiler/output_bundle.py index 153211f34f..67b4554285 100644 --- a/vyper/compiler/output_bundle.py +++ b/vyper/compiler/output_bundle.py @@ -254,6 +254,11 @@ def write_sources(self, sources: dict[str, CompilerInput]): for path, c in sources.items(): self.archive.writestr(_anonymize(path), c.contents) + def write_storage_layout_overrides( + self, compilation_target_path: str, storage_layout_override: StorageLayout + ): + self.archive.writestr("MANIFEST/storage_layout.json", json.dumps(storage_layout_override)) + def write_search_paths(self, search_paths: list[str]): self.archive.writestr("MANIFEST/searchpaths", "\n".join(search_paths)) From fda0e1ed54ec529137231697a4f961d89c541c24 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:40:00 +0800 Subject: [PATCH 09/38] Revert "include transient storage overrides" This reverts commit 08f039670066d9577ad620ac809a061ea65bbffb. --- vyper/cli/vyper_json.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index c6010f7463..001af6baff 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -215,12 +215,7 @@ def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayo raise JSONError(f"unknown target for storage layout override: {path}") path = PurePath(path) - storage_layout_overrides_for_path: StorageLayout = {} - for layout_type in ("storage_layout", "transient_storage_layout"): - if layout := value["content"].get(layout_type): - storage_layout_overrides_for_path.update(layout) - - storage_layout_overrides[path] = storage_layout_overrides_for_path + storage_layout_overrides[path] = value["content"]["storage_layout"] return storage_layout_overrides From 6e184ccd270afaaa68843436b0ff6f461b9c4ac4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:41:22 +0800 Subject: [PATCH 10/38] more reverts --- vyper/cli/vyper_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 001af6baff..9e0e6ad904 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -215,7 +215,7 @@ def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayo raise JSONError(f"unknown target for storage layout override: {path}") path = PurePath(path) - storage_layout_overrides[path] = value["content"]["storage_layout"] + storage_layout_overrides[path] = value["content"] return storage_layout_overrides From a3c18d7c569767b58ad6ce315b260700c71588d4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:41:28 +0800 Subject: [PATCH 11/38] fix tests --- .../test_storage_layout_overrides.py | 27 +++++++++++++++++++ .../cli/vyper_compile/test_compile_files.py | 12 ++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index 876b9d26f8..d7db30cc80 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -55,6 +55,33 @@ def test_storage_layout_overrides_json(): ) +def test_storage_layout_overrides_output(): + code = """ +a: uint256 +b: uint256""" + + storage_layout_overrides = { + "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "b": {"type": "uint256", "slot": 0, "n_slots": 1}, + } + + input_json = { + "language": "Vyper", + "sources": {"contracts/foo.vy": {"content": code}}, + "storage_layout_overrides": { + "contracts/foo.vy": {"content": {"storage_layout": storage_layout_overrides}} + }, + "settings": {"outputSelection": {"*": ["*"]}}, + } + + out = compile_code( + code, output_formats=["layout"], storage_layout_override=storage_layout_overrides + ) + assert compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == json.dumps( + out["layout"] + ) + + def test_storage_layout_for_more_complex(): code = """ foo: HashMap[address, uint256] diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index f54cc092e8..9d411b1956 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -310,10 +310,8 @@ def bar(x: uint256) -> uint256: return extcall jsonabi(msg.sender).test_json(x) """ storage_layout_overrides = { - "storage_layout": { - "a": {"type": "uint256", "n_slots": 1, "slot": 0}, - "b": {"type": "uint256", "n_slots": 1, "slot": 1}, - } + "a": {"type": "uint256", "n_slots": 1, "slot": 0}, + "b": {"type": "uint256", "n_slots": 1, "slot": 1}, } tmpdir = tmp_path_factory.mktemp("fake-package") @@ -342,10 +340,10 @@ def test_import_sys_path(input_files): def test_archive_output(input_files): - tmpdir, _, _, _, contract_file = input_files + tmpdir, _, _, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] - s = compile_files([contract_file], ["archive"], paths=search_paths) + s = compile_files([contract_file], ["archive"], paths=search_paths, storage_layout_paths=[storage_layout_path]) archive_bytes = s[contract_file]["archive"] archive_path = Path("foo.zip") @@ -355,7 +353,7 @@ def test_archive_output(input_files): assert zipfile.is_zipfile(archive_path) # compare compiling the two input bundles - out = compile_files([contract_file], ["integrity", "bytecode"], paths=search_paths) + out = compile_files([contract_file], ["integrity", "bytecode"], paths=search_paths, storage_layout_paths=[storage_layout_path]) out2 = compile_files([archive_path], ["integrity", "bytecode"]) assert out[contract_file] == out2[archive_path] From 73f650eba49628231979ebe6247ab1dd43d3e6b6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:55:41 +0800 Subject: [PATCH 12/38] fix test --- tests/unit/cli/vyper_compile/test_compile_files.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 9d411b1956..41f1c8553c 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -343,7 +343,9 @@ def test_archive_output(input_files): tmpdir, _, _, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] - s = compile_files([contract_file], ["archive"], paths=search_paths, storage_layout_paths=[storage_layout_path]) + s = compile_files( + [contract_file], ["archive"], paths=search_paths, storage_layout_paths=[storage_layout_path] + ) archive_bytes = s[contract_file]["archive"] archive_path = Path("foo.zip") @@ -353,7 +355,12 @@ def test_archive_output(input_files): assert zipfile.is_zipfile(archive_path) # compare compiling the two input bundles - out = compile_files([contract_file], ["integrity", "bytecode"], paths=search_paths, storage_layout_paths=[storage_layout_path]) + out = compile_files( + [contract_file], + ["integrity", "bytecode"], + paths=search_paths, + storage_layout_paths=[storage_layout_path], + ) out2 = compile_files([archive_path], ["integrity", "bytecode"]) assert out[contract_file] == out2[archive_path] From c552876128993f38a811ae3df538b646c8cb95c1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:55:50 +0800 Subject: [PATCH 13/38] check for storage layout in zip --- vyper/cli/compile_archive.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vyper/cli/compile_archive.py b/vyper/cli/compile_archive.py index 204443ed94..b160bf4cc1 100644 --- a/vyper/cli/compile_archive.py +++ b/vyper/cli/compile_archive.py @@ -45,7 +45,12 @@ def compiler_data_from_zip(file_name, settings, no_bytecode_metadata): fcontents = archive.read("MANIFEST/compilation_targets").decode("utf-8") compilation_targets = fcontents.splitlines() - storage_layout = json.loads(archive.read("MANIFEST/storage_layout.json").decode("utf-8")) + storage_layout_path = "MANIFEST/storage_layout.json" + storage_layout = ( + json.loads(archive.read(storage_layout_path).decode("utf-8")) + if storage_layout_path in archive.namelist() + else None + ) if len(compilation_targets) != 1: raise BadArchive("Multiple compilation targets not supported!") From a16d67eef4cbbbd97fc7c772612204abcaeecae4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 24 Nov 2024 17:01:59 +0800 Subject: [PATCH 14/38] fix more tests --- .../cli/storage_layout/test_storage_layout_overrides.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index d7db30cc80..bf92754666 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -41,9 +41,7 @@ def test_storage_layout_overrides_json(): input_json = { "language": "Vyper", "sources": {"contracts/foo.vy": {"content": code}}, - "storage_layout_overrides": { - "contracts/foo.vy": {"content": {"storage_layout": storage_layout_overrides}} - }, + "storage_layout_overrides": {"contracts/foo.vy": {"content": storage_layout_overrides}}, "settings": {"outputSelection": {"*": ["*"]}}, } @@ -68,9 +66,7 @@ def test_storage_layout_overrides_output(): input_json = { "language": "Vyper", "sources": {"contracts/foo.vy": {"content": code}}, - "storage_layout_overrides": { - "contracts/foo.vy": {"content": {"storage_layout": storage_layout_overrides}} - }, + "storage_layout_overrides": {"contracts/foo.vy": {"content": storage_layout_overrides}}, "settings": {"outputSelection": {"*": ["*"]}}, } From 2f59780de9059b3fd33413d7c52b1a79a2a8cd5e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:02:23 +0800 Subject: [PATCH 15/38] apply bts suggestions --- vyper/cli/compile_archive.py | 8 +++----- vyper/cli/vyper_json.py | 2 +- vyper/compiler/output_bundle.py | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/vyper/cli/compile_archive.py b/vyper/cli/compile_archive.py index b160bf4cc1..d1dd2588ad 100644 --- a/vyper/cli/compile_archive.py +++ b/vyper/cli/compile_archive.py @@ -46,11 +46,9 @@ def compiler_data_from_zip(file_name, settings, no_bytecode_metadata): compilation_targets = fcontents.splitlines() storage_layout_path = "MANIFEST/storage_layout.json" - storage_layout = ( - json.loads(archive.read(storage_layout_path).decode("utf-8")) - if storage_layout_path in archive.namelist() - else None - ) + storage_layout = None + if storage_layout_path in archive.namelist(): + storage_layout = json.loads(archive.read(storage_layout_path).decode("utf-8")) if len(compilation_targets) != 1: raise BadArchive("Multiple compilation targets not supported!") diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 9e0e6ad904..d54b2b8331 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -314,7 +314,7 @@ def compile_from_input_dict( res, warnings_dict = {}, {} warnings.simplefilter("always") for contract_path in compilation_targets: - storage_layout_override = storage_layout_overrides.get(contract_path, None) + storage_layout_override = storage_layout_overrides.get(contract_path) with warnings.catch_warnings(record=True) as caught_warnings: try: # use load_file to get a unique source_id diff --git a/vyper/compiler/output_bundle.py b/vyper/compiler/output_bundle.py index 67b4554285..f69cbace98 100644 --- a/vyper/compiler/output_bundle.py +++ b/vyper/compiler/output_bundle.py @@ -166,7 +166,7 @@ def write(self): self.write_settings(self.compiler_data.original_settings) self.write_integrity(self.compiler_data.resolved_imports.integrity_sum) self.write_sources(self.bundle.compiler_inputs) - if self.compiler_data.storage_layout_override: + if self.compiler_data.storage_layout_override is not None: self.write_storage_layout_overrides( self.bundle.compilation_target_path, self.compiler_data.storage_layout_override ) From a93c0b9c33970f23652bfaa2e950b880f8372824 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:14:11 +0800 Subject: [PATCH 16/38] pass storage layout --- vyper/compiler/phases.py | 4 +++- vyper/semantics/analysis/imports.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 503281a867..c75b09ecc0 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -152,7 +152,9 @@ def _resolve_imports(self): # deepcopy so as to not interfere with `-f ast` output vyper_module = copy.deepcopy(self.vyper_module) with self.input_bundle.search_path(Path(vyper_module.resolved_path).parent): - return vyper_module, resolve_imports(vyper_module, self.input_bundle) + return vyper_module, resolve_imports( + vyper_module, self.input_bundle, self.storage_layout_override + ) @cached_property def resolved_imports(self): diff --git a/vyper/semantics/analysis/imports.py b/vyper/semantics/analysis/imports.py index 3268f12e94..9d4cecbe93 100644 --- a/vyper/semantics/analysis/imports.py +++ b/vyper/semantics/analysis/imports.py @@ -1,7 +1,8 @@ import contextlib +import json from dataclasses import dataclass, field from pathlib import Path, PurePath -from typing import Any, Iterator +from typing import Any, Iterator, Optional import vyper.builtins.interfaces from vyper import ast as vy_ast @@ -21,6 +22,7 @@ StructureException, ) from vyper.semantics.analysis.base import ImportInfo +from vyper.typing import StorageLayout from vyper.utils import safe_relpath, sha256sum """ @@ -72,9 +74,15 @@ def enter_path(self, module_ast: vy_ast.Module) -> Iterator[None]: class ImportAnalyzer: - def __init__(self, input_bundle: InputBundle, graph: _ImportGraph): + def __init__( + self, + input_bundle: InputBundle, + graph: _ImportGraph, + storage_layout: Optional[StorageLayout], + ): self.input_bundle = input_bundle self.graph = graph + self.storage_layout = storage_layout self._ast_of: dict[int, vy_ast.Module] = {} self.seen: set[int] = set() @@ -96,6 +104,9 @@ def _calculate_integrity_sum_r(self, module_ast: vy_ast.Module): else: acc.append(self._calculate_integrity_sum_r(info.parsed)) + if self.storage_layout is not None: + acc.append(json.dumps(self.storage_layout)) + return sha256sum("".join(acc)) def _resolve_imports_r(self, module_ast: vy_ast.Module): @@ -325,9 +336,11 @@ def _load_builtin_import(level: int, module_str: str) -> tuple[CompilerInput, vy return file, interface_ast -def resolve_imports(module_ast: vy_ast.Module, input_bundle: InputBundle): +def resolve_imports( + module_ast: vy_ast.Module, input_bundle: InputBundle, storage_layout: StorageLayout +): graph = _ImportGraph() - analyzer = ImportAnalyzer(input_bundle, graph) + analyzer = ImportAnalyzer(input_bundle, graph, storage_layout) analyzer.resolve_imports(module_ast) return analyzer From 05f07868e412c36cc43f207ae5b39a775eca61c1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:43:02 +0800 Subject: [PATCH 17/38] apply bts suggestion --- vyper/cli/vyper_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index d54b2b8331..048bab069b 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -364,7 +364,7 @@ def format_to_output_dict(compiler_data: dict) -> dict: if key in data: output_contracts[key] = data[key] - if data.get("layout"): + if "layout" in data: output_contracts["layout"] = json.dumps(data["layout"]) if "method_identifiers" in data: From 5e8122ca2c6e845654160365628b2aa043bb026c Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:19:06 +0800 Subject: [PATCH 18/38] add layout to dict for tests --- tests/unit/cli/vyper_json/test_compile_json.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 7802ee7955..11adc6c13e 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -170,6 +170,7 @@ def test_compile_json(input_json, input_bundle): "interface": data["interface"], "ir": data["ir_dict"], "userdoc": data["userdoc"], + "layout": json.dumps(data["layout"]), "metadata": data["metadata"], "evm": { "bytecode": { @@ -217,7 +218,16 @@ def test_different_outputs(input_bundle, input_json): foo = contracts["contracts/foo.vy"]["foo"] bar = contracts["contracts/bar.vy"]["bar"] - assert sorted(bar.keys()) == ["abi", "devdoc", "evm", "interface", "ir", "metadata", "userdoc"] + assert sorted(bar.keys()) == [ + "abi", + "devdoc", + "evm", + "interface", + "ir", + "layout", + "metadata", + "userdoc", + ] assert sorted(foo.keys()) == ["evm"] From 7aac6f329b7eb147b932cc726bc20e33c809e024 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:41:39 +0800 Subject: [PATCH 19/38] fix solc json round trip test --- tests/unit/cli/vyper_compile/test_compile_files.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 41f1c8553c..241f8c7508 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -310,8 +310,8 @@ def bar(x: uint256) -> uint256: return extcall jsonabi(msg.sender).test_json(x) """ storage_layout_overrides = { - "a": {"type": "uint256", "n_slots": 1, "slot": 0}, - "b": {"type": "uint256", "n_slots": 1, "slot": 1}, + "a": {"type": "uint256", "n_slots": 1, "slot": 1}, + "b": {"type": "uint256", "n_slots": 1, "slot": 0}, } tmpdir = tmp_path_factory.mktemp("fake-package") @@ -387,13 +387,12 @@ def test_archive_b64_output(input_files): def test_solc_json_output(input_files): tmpdir, _, _, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] - storage_layout_paths = [storage_layout_path] out = compile_files( [contract_file], ["solc_json"], paths=search_paths, - storage_layout_paths=storage_layout_paths, + storage_layout_paths=[storage_layout_path], ) json_input = out[contract_file]["solc_json"] @@ -402,7 +401,12 @@ def test_solc_json_output(input_files): json_out = compile_json(json_input)["contracts"]["contract.vy"] json_out_bytecode = json_out["contract"]["evm"]["bytecode"]["object"] - out2 = compile_files([contract_file], ["integrity", "bytecode"], paths=search_paths) + out2 = compile_files( + [contract_file], + ["integrity", "bytecode"], + paths=search_paths, + storage_layout_paths=[storage_layout_path], + ) assert out2[contract_file]["bytecode"] == json_out_bytecode From 49fdd97b4cf3e4733bba65e803640f37e6bf11a4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:58:17 +0800 Subject: [PATCH 20/38] remove content key --- .../unit/cli/storage_layout/test_storage_layout_overrides.py | 4 ++-- vyper/cli/vyper_json.py | 2 +- vyper/compiler/output_bundle.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index bf92754666..b87c13e270 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -41,7 +41,7 @@ def test_storage_layout_overrides_json(): input_json = { "language": "Vyper", "sources": {"contracts/foo.vy": {"content": code}}, - "storage_layout_overrides": {"contracts/foo.vy": {"content": storage_layout_overrides}}, + "storage_layout_overrides": {"contracts/foo.vy": storage_layout_overrides}, "settings": {"outputSelection": {"*": ["*"]}}, } @@ -66,7 +66,7 @@ def test_storage_layout_overrides_output(): input_json = { "language": "Vyper", "sources": {"contracts/foo.vy": {"content": code}}, - "storage_layout_overrides": {"contracts/foo.vy": {"content": storage_layout_overrides}}, + "storage_layout_overrides": {"contracts/foo.vy": storage_layout_overrides}, "settings": {"outputSelection": {"*": ["*"]}}, } diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 048bab069b..4fb42c8523 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -215,7 +215,7 @@ def get_storage_layout_overrides(input_dict: dict) -> dict[PurePath, StorageLayo raise JSONError(f"unknown target for storage layout override: {path}") path = PurePath(path) - storage_layout_overrides[path] = value["content"] + storage_layout_overrides[path] = value return storage_layout_overrides diff --git a/vyper/compiler/output_bundle.py b/vyper/compiler/output_bundle.py index f69cbace98..3eb6bac6f1 100644 --- a/vyper/compiler/output_bundle.py +++ b/vyper/compiler/output_bundle.py @@ -189,7 +189,7 @@ def write_storage_layout_overrides( self, compilation_target_path: str, storage_layout_override: StorageLayout ): self._output["storage_layout_overrides"] = { - compilation_target_path: {"content": storage_layout_override} + compilation_target_path: storage_layout_override } def write_search_paths(self, search_paths: list[str]): From 8d15e39949ac2a90763e1270722f7d83721e84d9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:14:03 +0800 Subject: [PATCH 21/38] fix typo in docs --- docs/compiling-a-contract.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/compiling-a-contract.rst b/docs/compiling-a-contract.rst index 7132cff58d..7eab8b98a8 100644 --- a/docs/compiling-a-contract.rst +++ b/docs/compiling-a-contract.rst @@ -106,7 +106,7 @@ Online Compilers Try VyperLang! ----------------- -`Try VyperLang! `_ is a JupterHub instance hosted by the Vyper team as a sandbox for developing and testing contracts in Vyper. It requires github for login, and supports deployment via the browser. +`Try VyperLang! `_ is a JupyterHub instance hosted by the Vyper team as a sandbox for developing and testing contracts in Vyper. It requires github for login, and supports deployment via the browser. Remix IDE --------- From c8f42e1416109ccbdd099c15177ed395f8a447a1 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:27:51 +0800 Subject: [PATCH 22/38] update docs --- docs/compiling-a-contract.rst | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/compiling-a-contract.rst b/docs/compiling-a-contract.rst index 7eab8b98a8..c839e1e81d 100644 --- a/docs/compiling-a-contract.rst +++ b/docs/compiling-a-contract.rst @@ -203,7 +203,7 @@ The following is a list of supported EVM versions, and changes in the compiler i Integrity Hash ============== -To help tooling detect whether two builds are the same, Vyper provides the ``-f integrity`` output, which outputs the integrity hash of a contract. The integrity hash is recursively defined as the sha256 of the source code with the integrity hashes of its dependencies (imports). +To help tooling detect whether two builds are the same, Vyper provides the ``-f integrity`` output, which outputs the integrity hash of a contract. The integrity hash is recursively defined as the sha256 of the source code with the integrity hashes of its dependencies (imports) and storage layout overrides (if provided). .. _vyper-archives: @@ -219,8 +219,9 @@ A Vyper archive is a compileable bundle of input sources and settings. Technical ├── compilation_targets ├── compiler_version ├── integrity + ├── settings.json ├── searchpaths - └── settings.json + └── storage_layout.json [OPTIONAL] * ``cli_settings.txt`` is a text representation of the settings that were used on the compilation run that generated this archive. * ``compilation_targets`` is a newline separated list of compilation targets. Currently only one compilation is supported @@ -228,6 +229,7 @@ A Vyper archive is a compileable bundle of input sources and settings. Technical * ``integrity`` is the :ref:`integrity hash ` of the input contract * ``searchpaths`` is a newline-separated list of the search paths used on this compilation run * ``settings.json`` is a json representation of the settings used on this compilation run. It is 1:1 with ``cli_settings.txt``, but both are provided as they are convenient for different workflows (typically, manually vs automated). +* ``storage_layout.json`` is a json representation of the storage layout overrides to be used on this compilation run. It is optional. A Vyper archive file can be produced by requesting the ``-f archive`` output format. The compiler can also produce the archive in base64 encoded form using the ``--base64`` flag. The Vyper compiler can accept both ``.vyz`` and base64-encoded Vyper archives directly as input. @@ -281,6 +283,14 @@ The following example describes the expected input format of ``vyper-json``. (Co } }, // Optional + // Storage layout overrides for the contracts that are compiled + "storage_layout_overrides": { + "contracts/foo.vy": { + "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "b": {"type": "uint256", "slot": 0, "n_slots": 1}, + } + }, + // Optional "settings": { "evmVersion": "cancun", // EVM version to compile for. Can be london, paris, shanghai or cancun (default). // optional, optimization mode @@ -364,6 +374,13 @@ The following example describes the output format of ``vyper-json``. Comments ar "formattedMessage": "line 5:11 Unsupported type conversion: int128 to bool" } ], + // Optional: not present if there are no storage layout overrides + "storage_layout_overrides": { + "contracts/foo.vy": { + "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "b": {"type": "uint256", "slot": 0, "n_slots": 1}, + } + }, // This contains the file-level outputs. Can be limited/filtered by the outputSelection settings. "sources": { "source_file.vy": { From 4036b67d8eb249d1879fb4dd83cab4ce07730d00 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:38:37 +0800 Subject: [PATCH 23/38] undo passing down of storage layout --- vyper/compiler/phases.py | 4 +--- vyper/semantics/analysis/imports.py | 21 ++++----------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index c75b09ecc0..503281a867 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -152,9 +152,7 @@ def _resolve_imports(self): # deepcopy so as to not interfere with `-f ast` output vyper_module = copy.deepcopy(self.vyper_module) with self.input_bundle.search_path(Path(vyper_module.resolved_path).parent): - return vyper_module, resolve_imports( - vyper_module, self.input_bundle, self.storage_layout_override - ) + return vyper_module, resolve_imports(vyper_module, self.input_bundle) @cached_property def resolved_imports(self): diff --git a/vyper/semantics/analysis/imports.py b/vyper/semantics/analysis/imports.py index 9d4cecbe93..3268f12e94 100644 --- a/vyper/semantics/analysis/imports.py +++ b/vyper/semantics/analysis/imports.py @@ -1,8 +1,7 @@ import contextlib -import json from dataclasses import dataclass, field from pathlib import Path, PurePath -from typing import Any, Iterator, Optional +from typing import Any, Iterator import vyper.builtins.interfaces from vyper import ast as vy_ast @@ -22,7 +21,6 @@ StructureException, ) from vyper.semantics.analysis.base import ImportInfo -from vyper.typing import StorageLayout from vyper.utils import safe_relpath, sha256sum """ @@ -74,15 +72,9 @@ def enter_path(self, module_ast: vy_ast.Module) -> Iterator[None]: class ImportAnalyzer: - def __init__( - self, - input_bundle: InputBundle, - graph: _ImportGraph, - storage_layout: Optional[StorageLayout], - ): + def __init__(self, input_bundle: InputBundle, graph: _ImportGraph): self.input_bundle = input_bundle self.graph = graph - self.storage_layout = storage_layout self._ast_of: dict[int, vy_ast.Module] = {} self.seen: set[int] = set() @@ -104,9 +96,6 @@ def _calculate_integrity_sum_r(self, module_ast: vy_ast.Module): else: acc.append(self._calculate_integrity_sum_r(info.parsed)) - if self.storage_layout is not None: - acc.append(json.dumps(self.storage_layout)) - return sha256sum("".join(acc)) def _resolve_imports_r(self, module_ast: vy_ast.Module): @@ -336,11 +325,9 @@ def _load_builtin_import(level: int, module_str: str) -> tuple[CompilerInput, vy return file, interface_ast -def resolve_imports( - module_ast: vy_ast.Module, input_bundle: InputBundle, storage_layout: StorageLayout -): +def resolve_imports(module_ast: vy_ast.Module, input_bundle: InputBundle): graph = _ImportGraph() - analyzer = ImportAnalyzer(input_bundle, graph, storage_layout) + analyzer = ImportAnalyzer(input_bundle, graph) analyzer.resolve_imports(module_ast) return analyzer From 43f5643619501bf728771727dde91b26c913a0d8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:45:28 +0800 Subject: [PATCH 24/38] apply cc suggestion on refactoring integrity hash --- vyper/compiler/output.py | 2 +- vyper/compiler/output_bundle.py | 2 +- vyper/compiler/phases.py | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index e0eea293bc..b5881dfbe8 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -102,7 +102,7 @@ def build_archive_b64(compiler_data: CompilerData) -> str: def build_integrity(compiler_data: CompilerData) -> str: - return compiler_data.resolved_imports.integrity_sum + return compiler_data.integrity_sum def build_external_interface_output(compiler_data: CompilerData) -> str: diff --git a/vyper/compiler/output_bundle.py b/vyper/compiler/output_bundle.py index 3eb6bac6f1..8af1b72289 100644 --- a/vyper/compiler/output_bundle.py +++ b/vyper/compiler/output_bundle.py @@ -164,7 +164,7 @@ def write(self): self.write_compilation_target([self.bundle.compilation_target_path]) self.write_search_paths(self.bundle.used_search_paths) self.write_settings(self.compiler_data.original_settings) - self.write_integrity(self.compiler_data.resolved_imports.integrity_sum) + self.write_integrity(self.compiler_data.integrity_sum) self.write_sources(self.bundle.compiler_inputs) if self.compiler_data.storage_layout_override is not None: self.write_storage_layout_overrides( diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 503281a867..b834269ce7 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -1,4 +1,5 @@ import copy +import json import warnings from functools import cached_property from pathlib import Path, PurePath @@ -18,7 +19,7 @@ from vyper.semantics.types.function import ContractFunctionT from vyper.semantics.types.module import ModuleT from vyper.typing import StorageLayout -from vyper.utils import ERC5202_PREFIX, vyper_warn +from vyper.utils import ERC5202_PREFIX, sha256sum, vyper_warn from vyper.venom import generate_assembly_experimental, generate_ir DEFAULT_CONTRACT_PATH = PurePath("VyperContract.vy") @@ -267,7 +268,7 @@ def assembly_runtime(self) -> list: def bytecode(self) -> bytes: metadata = None if not self.no_bytecode_metadata: - metadata = bytes.fromhex(self.resolved_imports.integrity_sum) + metadata = bytes.fromhex(self.integrity_sum) return generate_bytecode(self.assembly, compiler_metadata=metadata) @cached_property @@ -284,6 +285,15 @@ def blueprint_bytecode(self) -> bytes: return deploy_bytecode + blueprint_bytecode + @cached_property + def integrity_sum(self) -> str: + if self.storage_layout_override: + return sha256sum( + sha256sum(json.dumps(self.storage_layout_override)) + + self.resolved_imports.integrity_sum + ) + return self.resolved_imports.integrity_sum + def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode, IRnode]: """ From 49986e5e95901ea522eb432e64394843d1f590dc Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:47:48 +0800 Subject: [PATCH 25/38] remove dup test --- .../test_storage_layout_overrides.py | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index b87c13e270..43d74d907b 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -53,31 +53,6 @@ def test_storage_layout_overrides_json(): ) -def test_storage_layout_overrides_output(): - code = """ -a: uint256 -b: uint256""" - - storage_layout_overrides = { - "a": {"type": "uint256", "slot": 1, "n_slots": 1}, - "b": {"type": "uint256", "slot": 0, "n_slots": 1}, - } - - input_json = { - "language": "Vyper", - "sources": {"contracts/foo.vy": {"content": code}}, - "storage_layout_overrides": {"contracts/foo.vy": storage_layout_overrides}, - "settings": {"outputSelection": {"*": ["*"]}}, - } - - out = compile_code( - code, output_formats=["layout"], storage_layout_override=storage_layout_overrides - ) - assert compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == json.dumps( - out["layout"] - ) - - def test_storage_layout_for_more_complex(): code = """ foo: HashMap[address, uint256] From 58c3064eee212e6900a2c0b305fce0c6b83ee845 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 10 Dec 2024 11:47:56 -0500 Subject: [PATCH 26/38] fix integrity sum check and small refactor --- vyper/compiler/phases.py | 37 ++++++++++++++++------------- vyper/semantics/analysis/imports.py | 4 ++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index b834269ce7..3d5791a644 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -148,29 +148,41 @@ def vyper_module(self): _, ast = self._generate_ast return ast + def _compute_integrity_sum(self, imports_integrity_sum: str) -> str: + if self.storage_layout_override is not None: + layout_sum = sha256sum(json.dumps(self.storage_layout_override)) + return sha256sum(layout_sum + imports_integrity_sum) + return imports_integrity_sum + @cached_property def _resolve_imports(self): # deepcopy so as to not interfere with `-f ast` output vyper_module = copy.deepcopy(self.vyper_module) with self.input_bundle.search_path(Path(vyper_module.resolved_path).parent): - return vyper_module, resolve_imports(vyper_module, self.input_bundle) + imports = resolve_imports(vyper_module, self.input_bundle) - @cached_property - def resolved_imports(self): - imports = self._resolve_imports[1] + # check integrity sum + integrity_sum = self._compute_integrity_sum(imports._integrity_sum) expected = self.expected_integrity_sum - - if expected is not None and imports.integrity_sum != expected: + if expected is not None and integrity_sum != expected: # warn for now. strict/relaxed mode was considered but it costs # interface and testing complexity to add another feature flag. vyper_warn( f"Mismatched integrity sum! Expected {expected}" - f" but got {imports.integrity_sum}." + f" but got {integrity_sum}." " (This likely indicates a corrupted archive)" ) - return imports + return vyper_module, imports, integrity_sum + + @cached_property + def integrity_sum(self): + return self._resolve_imports[2] + + @cached_property + def resolved_imports(self): + return self._resolve_imports[1] @cached_property def _annotate(self) -> tuple[natspec.NatspecOutput, vy_ast.Module]: @@ -285,15 +297,6 @@ def blueprint_bytecode(self) -> bytes: return deploy_bytecode + blueprint_bytecode - @cached_property - def integrity_sum(self) -> str: - if self.storage_layout_override: - return sha256sum( - sha256sum(json.dumps(self.storage_layout_override)) - + self.resolved_imports.integrity_sum - ) - return self.resolved_imports.integrity_sum - def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode, IRnode]: """ diff --git a/vyper/semantics/analysis/imports.py b/vyper/semantics/analysis/imports.py index 3268f12e94..0a160639d2 100644 --- a/vyper/semantics/analysis/imports.py +++ b/vyper/semantics/analysis/imports.py @@ -79,11 +79,11 @@ def __init__(self, input_bundle: InputBundle, graph: _ImportGraph): self.seen: set[int] = set() - self.integrity_sum = None + self._integrity_sum = None def resolve_imports(self, module_ast: vy_ast.Module): self._resolve_imports_r(module_ast) - self.integrity_sum = self._calculate_integrity_sum_r(module_ast) + self._integrity_sum = self._calculate_integrity_sum_r(module_ast) def _calculate_integrity_sum_r(self, module_ast: vy_ast.Module): acc = [sha256sum(module_ast.full_source_code)] From e00be88ff572c8aeacb2264517b4fdc74bd3c0f4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:59:41 +0800 Subject: [PATCH 27/38] add tests --- .../cli/vyper_compile/test_compile_files.py | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 241f8c7508..e5e4067740 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -1,6 +1,8 @@ import contextlib +import io import json import sys +import warnings import zipfile from pathlib import Path @@ -340,7 +342,7 @@ def test_import_sys_path(input_files): def test_archive_output(input_files): - tmpdir, _, _, storage_layout_path, contract_file = input_files + tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] s = compile_files( @@ -364,6 +366,50 @@ def test_archive_output(input_files): out2 = compile_files([archive_path], ["integrity", "bytecode"]) assert out[contract_file] == out2[archive_path] + # tamper the integrity sum by using the resolved imports hash, which excludes the storage layout + with ( + library_file.open() as f, + contract_file.open() as g, + jsonabi_file.open() as h, + storage_layout_path.open() as i, + ): + library_contents = f.read() + contract_contents = g.read() + jsonabi_contents = h.read() + storage_layout_contents = i.read() + + contract_hash = sha256sum(contract_contents) + library_hash = sha256sum(library_contents) + jsonabi_hash = sha256sum(jsonabi_contents) + resolved_imports_hash = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) + storage_layout_hash = sha256sum(storage_layout_contents) + expected_hash = sha256sum(storage_layout_hash + resolved_imports_hash) + + integrity_filename = "MANIFEST/integrity" + with zipfile.ZipFile(archive_path, "r") as zip_read: + memory_zip = io.BytesIO() + + with zipfile.ZipFile(memory_zip, "w") as zip_write: + for item in zip_read.infolist(): + if item.filename == integrity_filename: + zip_write.writestr(item, resolved_imports_hash.encode()) + else: + zip_write.writestr(item, zip_read.read(item.filename)) + + memory_zip.seek(0) + with open(archive_path, "wb") as f: + f.write(memory_zip.read()) + + with warnings.catch_warnings(record=True) as w: + assert compile_files([archive_path], ["integrity", "bytecode"]) is not None + + expected = f"Mismatched integrity sum! Expected {resolved_imports_hash}" + expected += f" but got {expected_hash}." + expected += " (This likely indicates a corrupted archive)" + + assert len(w) == 1, [s.message for s in w] + assert str(w[0].message).startswith(expected) + def test_archive_b64_output(input_files): tmpdir, _, _, _, contract_file = input_files @@ -413,20 +459,33 @@ def test_solc_json_output(input_files): # maybe this belongs in tests/unit/compiler? def test_integrity_sum(input_files): - tmpdir, library_file, jsonabi_file, _, contract_file = input_files + tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file = input_files search_paths = [".", tmpdir] - out = compile_files([contract_file], ["integrity"], paths=search_paths) + out = compile_files( + [contract_file], + ["integrity"], + paths=search_paths, + storage_layout_paths=[storage_layout_path], + ) - with library_file.open() as f, contract_file.open() as g, jsonabi_file.open() as h: + with ( + library_file.open() as f, + contract_file.open() as g, + jsonabi_file.open() as h, + storage_layout_path.open() as i, + ): library_contents = f.read() contract_contents = g.read() jsonabi_contents = h.read() + storage_layout_contents = i.read() contract_hash = sha256sum(contract_contents) library_hash = sha256sum(library_contents) jsonabi_hash = sha256sum(jsonabi_contents) - expected = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) + resolved_imports_hash = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) + storage_layout_hash = sha256sum(storage_layout_contents) + expected = sha256sum(storage_layout_hash + resolved_imports_hash) assert out[contract_file]["integrity"] == expected From eea76e20fea6e762c338745f44ac145b98786ab2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:43:05 +0800 Subject: [PATCH 28/38] refactor tests --- .../cli/vyper_compile/test_compile_files.py | 102 +++++++----------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index e5e4067740..5edb76f3ea 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -1,5 +1,4 @@ import contextlib -import io import json import sys import warnings @@ -8,13 +7,20 @@ import pytest +from vyper.cli.compile_archive import compiler_data_from_zip from vyper.cli.vyper_compile import compile_files -from vyper.cli.vyper_json import compile_json +from vyper.cli.vyper_json import compile_from_input_dict, compile_json from vyper.compiler.input_bundle import FilesystemInputBundle from vyper.compiler.output_bundle import OutputBundle from vyper.compiler.phases import CompilerData from vyper.utils import sha256sum +TAMPERED_INTEGRITY_SUM = sha256sum("tampered integrity sum") + +INTEGRITY_WARNING = f"Mismatched integrity sum! Expected {TAMPERED_INTEGRITY_SUM}" +INTEGRITY_WARNING += " but got {integrity}." # noqa: FS003 +INTEGRITY_WARNING += " (This likely indicates a corrupted archive)" + def test_combined_json_keys(chdir_tmp_path, make_file): make_file("bar.vy", "") @@ -315,6 +321,7 @@ def bar(x: uint256) -> uint256: "a": {"type": "uint256", "n_slots": 1, "slot": 1}, "b": {"type": "uint256", "n_slots": 1, "slot": 0}, } + storage_layout_source = json.dumps(storage_layout_overrides) tmpdir = tmp_path_factory.mktemp("fake-package") with open(tmpdir / "lib.vy", "w") as f: @@ -322,27 +329,35 @@ def bar(x: uint256) -> uint256: with open(tmpdir / "jsonabi.json", "w") as f: f.write(json_source) with open(tmpdir / "layout.json", "w") as f: - f.write(json.dumps(storage_layout_overrides)) + f.write(storage_layout_source) contract_file = make_file("contract.vy", contract_source) + contract_hash = sha256sum(contract_source) + library_hash = sha256sum(library_source) + jsonabi_hash = sha256sum(json_source) + resolved_imports_hash = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) + storage_layout_hash = sha256sum(storage_layout_source) + expected_integrity = sha256sum(storage_layout_hash + resolved_imports_hash) + return ( tmpdir, tmpdir / "lib.vy", tmpdir / "jsonabi.json", tmpdir / "layout.json", contract_file, + expected_integrity, ) def test_import_sys_path(input_files): - tmpdir, _, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file, _ = input_files with mock_sys_path(tmpdir): assert compile_files([contract_file], ["combined_json"]) is not None def test_archive_output(input_files): - tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file = input_files + tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file, integrity = input_files search_paths = [".", tmpdir] s = compile_files( @@ -366,53 +381,19 @@ def test_archive_output(input_files): out2 = compile_files([archive_path], ["integrity", "bytecode"]) assert out[contract_file] == out2[archive_path] - # tamper the integrity sum by using the resolved imports hash, which excludes the storage layout - with ( - library_file.open() as f, - contract_file.open() as g, - jsonabi_file.open() as h, - storage_layout_path.open() as i, - ): - library_contents = f.read() - contract_contents = g.read() - jsonabi_contents = h.read() - storage_layout_contents = i.read() - - contract_hash = sha256sum(contract_contents) - library_hash = sha256sum(library_contents) - jsonabi_hash = sha256sum(jsonabi_contents) - resolved_imports_hash = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) - storage_layout_hash = sha256sum(storage_layout_contents) - expected_hash = sha256sum(storage_layout_hash + resolved_imports_hash) - - integrity_filename = "MANIFEST/integrity" - with zipfile.ZipFile(archive_path, "r") as zip_read: - memory_zip = io.BytesIO() - - with zipfile.ZipFile(memory_zip, "w") as zip_write: - for item in zip_read.infolist(): - if item.filename == integrity_filename: - zip_write.writestr(item, resolved_imports_hash.encode()) - else: - zip_write.writestr(item, zip_read.read(item.filename)) - - memory_zip.seek(0) - with open(archive_path, "wb") as f: - f.write(memory_zip.read()) + # tamper with the integrity sum + archive_compiler_data = compiler_data_from_zip(archive_path, None, False) + archive_compiler_data.expected_integrity_sum = TAMPERED_INTEGRITY_SUM with warnings.catch_warnings(record=True) as w: - assert compile_files([archive_path], ["integrity", "bytecode"]) is not None - - expected = f"Mismatched integrity sum! Expected {resolved_imports_hash}" - expected += f" but got {expected_hash}." - expected += " (This likely indicates a corrupted archive)" + assert archive_compiler_data.integrity_sum is not None assert len(w) == 1, [s.message for s in w] - assert str(w[0].message).startswith(expected) + assert str(w[0].message).startswith(INTEGRITY_WARNING.format(integrity=integrity)) def test_archive_b64_output(input_files): - tmpdir, _, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file, _ = input_files search_paths = [".", tmpdir] out = compile_files( @@ -431,7 +412,7 @@ def test_archive_b64_output(input_files): def test_solc_json_output(input_files): - tmpdir, _, _, storage_layout_path, contract_file = input_files + tmpdir, _, _, storage_layout_path, contract_file, integrity = input_files search_paths = [".", tmpdir] out = compile_files( @@ -456,10 +437,18 @@ def test_solc_json_output(input_files): assert out2[contract_file]["bytecode"] == json_out_bytecode + # tamper with the integrity sum + json_input["integrity"] = TAMPERED_INTEGRITY_SUM + _, warn_data = compile_from_input_dict(json_input) + + w = warn_data[Path("contract.vy")] + assert len(w) == 1, [s.message for s in w] + assert str(w[0].message).startswith(INTEGRITY_WARNING.format(integrity=integrity)) + # maybe this belongs in tests/unit/compiler? def test_integrity_sum(input_files): - tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file = input_files + tmpdir, library_file, jsonabi_file, storage_layout_path, contract_file, integrity = input_files search_paths = [".", tmpdir] out = compile_files( @@ -469,24 +458,7 @@ def test_integrity_sum(input_files): storage_layout_paths=[storage_layout_path], ) - with ( - library_file.open() as f, - contract_file.open() as g, - jsonabi_file.open() as h, - storage_layout_path.open() as i, - ): - library_contents = f.read() - contract_contents = g.read() - jsonabi_contents = h.read() - storage_layout_contents = i.read() - - contract_hash = sha256sum(contract_contents) - library_hash = sha256sum(library_contents) - jsonabi_hash = sha256sum(jsonabi_contents) - resolved_imports_hash = sha256sum(contract_hash + sha256sum(library_hash) + jsonabi_hash) - storage_layout_hash = sha256sum(storage_layout_contents) - expected = sha256sum(storage_layout_hash + resolved_imports_hash) - assert out[contract_file]["integrity"] == expected + assert out[contract_file]["integrity"] == integrity # does this belong in tests/unit/compiler? From 848ea345ff78d6b643bc94b010d10dcc28bbcf63 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:15:35 +0800 Subject: [PATCH 29/38] add round trip layout tests --- tests/unit/cli/vyper_compile/test_compile_files.py | 6 +++--- tests/unit/cli/vyper_json/test_compile_json.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 5edb76f3ea..19ca5aa9f0 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -374,11 +374,11 @@ def test_archive_output(input_files): # compare compiling the two input bundles out = compile_files( [contract_file], - ["integrity", "bytecode"], + ["integrity", "bytecode", "layout"], paths=search_paths, storage_layout_paths=[storage_layout_path], ) - out2 = compile_files([archive_path], ["integrity", "bytecode"]) + out2 = compile_files([archive_path], ["integrity", "bytecode", "layout"]) assert out[contract_file] == out2[archive_path] # tamper with the integrity sum @@ -430,7 +430,7 @@ def test_solc_json_output(input_files): out2 = compile_files( [contract_file], - ["integrity", "bytecode"], + ["integrity", "bytecode", "layout"], paths=search_paths, storage_layout_paths=[storage_layout_path], ) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 11adc6c13e..5f841dd572 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -20,6 +20,9 @@ import contracts.library as library +a: uint256 +b: uint256 + @external def foo(a: address) -> bool: return extcall IBar(a).bar(1) @@ -29,6 +32,11 @@ def baz() -> uint256: return self.balance + library.foo() """ +FOO_STORAGE_LAYOUT_OVERRIDES = { + "a": {"type": "uint256", "n_slots": 1, "slot": 1}, + "b": {"type": "uint256", "n_slots": 1, "slot": 0}, +} + BAR_CODE = """ import contracts.ibar as IBar @@ -88,6 +96,7 @@ def input_json(optimize, evm_version, experimental_codegen): "evmVersion": evm_version, "experimentalCodegen": experimental_codegen, }, + "storage_layout_overrides": {"contracts/foo.vy": FOO_STORAGE_LAYOUT_OVERRIDES}, } @@ -127,7 +136,10 @@ def test_compile_json(input_json, input_bundle): del output_formats["cfg"] del output_formats["cfg_runtime"] foo = compile_from_file_input( - foo_input, output_formats=output_formats, input_bundle=input_bundle + foo_input, + output_formats=output_formats, + input_bundle=input_bundle, + storage_layout_override=FOO_STORAGE_LAYOUT_OVERRIDES, ) library_input = input_bundle.load_file("contracts/library.vy") From 5d3c9467a19e59e0092dba84f25cae648d94649f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:17:56 +0800 Subject: [PATCH 30/38] change to slot 5 --- tests/unit/cli/storage_layout/test_storage_layout_overrides.py | 2 +- tests/unit/cli/vyper_compile/test_compile_files.py | 2 +- tests/unit/cli/vyper_json/test_compile_json.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index 43d74d907b..121bff6dbe 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -15,7 +15,7 @@ def test_storage_layout_overrides(): b: uint256""" storage_layout_overrides = { - "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "a": {"type": "uint256", "slot": 5, "n_slots": 1}, "b": {"type": "uint256", "slot": 0, "n_slots": 1}, } diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 19ca5aa9f0..1786c7e949 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -318,7 +318,7 @@ def bar(x: uint256) -> uint256: return extcall jsonabi(msg.sender).test_json(x) """ storage_layout_overrides = { - "a": {"type": "uint256", "n_slots": 1, "slot": 1}, + "a": {"type": "uint256", "n_slots": 1, "slot": 5}, "b": {"type": "uint256", "n_slots": 1, "slot": 0}, } storage_layout_source = json.dumps(storage_layout_overrides) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 5f841dd572..3f064019dc 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -33,7 +33,7 @@ def baz() -> uint256: """ FOO_STORAGE_LAYOUT_OVERRIDES = { - "a": {"type": "uint256", "n_slots": 1, "slot": 1}, + "a": {"type": "uint256", "n_slots": 1, "slot": 5}, "b": {"type": "uint256", "n_slots": 1, "slot": 0}, } From 4183a23c0f16c0277ce3a4700582f9f762e10b7f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:24:02 +0800 Subject: [PATCH 31/38] add json layout assertion --- tests/unit/cli/vyper_compile/test_compile_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 1786c7e949..f638e1e232 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -427,6 +427,7 @@ def test_solc_json_output(input_files): # the same as compiling directly json_out = compile_json(json_input)["contracts"]["contract.vy"] json_out_bytecode = json_out["contract"]["evm"]["bytecode"]["object"] + json_out_layout = json.loads(json_out["contract"]["layout"])["storage_layout"] out2 = compile_files( [contract_file], @@ -436,6 +437,7 @@ def test_solc_json_output(input_files): ) assert out2[contract_file]["bytecode"] == json_out_bytecode + assert out2[contract_file]["layout"]["storage_layout"] == json_out_layout # tamper with the integrity sum json_input["integrity"] = TAMPERED_INTEGRITY_SUM From 2cdff967a19aad384c87444bf40556286de82993 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 13 Dec 2024 11:23:08 -0500 Subject: [PATCH 32/38] make layout output in json not a string --- tests/unit/cli/vyper_json/test_compile_json.py | 2 +- vyper/cli/vyper_json.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 3f064019dc..1bde4400d2 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -182,7 +182,7 @@ def test_compile_json(input_json, input_bundle): "interface": data["interface"], "ir": data["ir_dict"], "userdoc": data["userdoc"], - "layout": json.dumps(data["layout"]), + "layout": data["layout"], "metadata": data["metadata"], "evm": { "bytecode": { diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py index 4fb42c8523..f7bcb622c7 100755 --- a/vyper/cli/vyper_json.py +++ b/vyper/cli/vyper_json.py @@ -365,7 +365,7 @@ def format_to_output_dict(compiler_data: dict) -> dict: output_contracts[key] = data[key] if "layout" in data: - output_contracts["layout"] = json.dumps(data["layout"]) + output_contracts["layout"] = data["layout"] if "method_identifiers" in data: output_contracts["evm"] = {"methodIdentifiers": data["method_identifiers"]} From bd3af726174c13ff6552611d7638264345dd0d4a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 13 Dec 2024 11:24:32 -0500 Subject: [PATCH 33/38] fix a test --- .../unit/cli/storage_layout/test_storage_layout_overrides.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index 121bff6dbe..0b52fdc9ac 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -1,4 +1,3 @@ -import json import re import pytest @@ -48,8 +47,8 @@ def test_storage_layout_overrides_json(): out = compile_code( code, output_formats=["layout"], storage_layout_override=storage_layout_overrides ) - assert compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == json.dumps( - out["layout"] + assert ( + compile_json(input_json)["contracts"]["contracts/foo.vy"]["foo"]["layout"] == out["layout"] ) From 9fbe4ba1586015ab72a78104ccab1e70822539e4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:57:44 +0800 Subject: [PATCH 34/38] add unknown path test --- tests/unit/cli/vyper_json/test_compile_json.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 1bde4400d2..c2ca6dbe12 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -291,6 +291,14 @@ def test_exc_handler_to_dict_compiler(input_json): assert error["type"] == "TypeMismatch" +def test_unknown_storage_layout_overrides(input_json): + unknown_contract_path = "contracts/baz.vy" + input_json["storage_layout_overrides"] = {unknown_contract_path: FOO_STORAGE_LAYOUT_OVERRIDES} + with pytest.raises(JSONError) as e: + compile_json(input_json) + assert e.value.args[0] == f"unknown target for storage layout override: {unknown_contract_path}" + + def test_source_ids_increment(input_json): input_json["settings"]["outputSelection"] = {"*": ["ast", "evm.deployedBytecode.sourceMap"]} result = compile_json(input_json) From 196bbc86b66563ecec0243cf1789dae2847160b6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:57:49 +0800 Subject: [PATCH 35/38] fix test --- tests/unit/cli/vyper_compile/test_compile_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index f638e1e232..7660930c26 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -427,7 +427,7 @@ def test_solc_json_output(input_files): # the same as compiling directly json_out = compile_json(json_input)["contracts"]["contract.vy"] json_out_bytecode = json_out["contract"]["evm"]["bytecode"]["object"] - json_out_layout = json.loads(json_out["contract"]["layout"])["storage_layout"] + json_out_layout = json_out["contract"]["layout"]["storage_layout"] out2 = compile_files( [contract_file], From b693b5c5f3e963573c84b98a24b81253af0816e6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:01:53 +0800 Subject: [PATCH 36/38] fix storage layout slot --- tests/unit/cli/storage_layout/test_storage_layout_overrides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py index 0b52fdc9ac..d2c495a2fa 100644 --- a/tests/unit/cli/storage_layout/test_storage_layout_overrides.py +++ b/tests/unit/cli/storage_layout/test_storage_layout_overrides.py @@ -33,7 +33,7 @@ def test_storage_layout_overrides_json(): b: uint256""" storage_layout_overrides = { - "a": {"type": "uint256", "slot": 1, "n_slots": 1}, + "a": {"type": "uint256", "slot": 5, "n_slots": 1}, "b": {"type": "uint256", "slot": 0, "n_slots": 1}, } From f640b3ece0f22cd0a38600d89c8b0887bdba76e8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:46:59 +0800 Subject: [PATCH 37/38] add multiple layouts for json input --- tests/unit/cli/vyper_json/test_compile_json.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index 329a87efac..0f932ba972 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -42,11 +42,19 @@ def baz() -> uint256: implements: IBar +c: uint256 +d: uint256 + @external def bar(a: uint256) -> bool: return True """ +BAR_STORAGE_LAYOUT_OVERRIDES = { + "c": {"type": "uint256", "n_slots": 1, "slot": 13}, + "d": {"type": "uint256", "n_slots": 1, "slot": 7}, +} + BAR_VYI = """ @external def bar(a: uint256) -> bool: @@ -96,7 +104,10 @@ def input_json(optimize, evm_version, experimental_codegen): "evmVersion": evm_version, "experimentalCodegen": experimental_codegen, }, - "storage_layout_overrides": {"contracts/foo.vy": FOO_STORAGE_LAYOUT_OVERRIDES}, + "storage_layout_overrides": { + "contracts/foo.vy": FOO_STORAGE_LAYOUT_OVERRIDES, + "contracts/bar.vy": BAR_STORAGE_LAYOUT_OVERRIDES, + }, } @@ -149,7 +160,10 @@ def test_compile_json(input_json, input_bundle): bar_input = input_bundle.load_file("contracts/bar.vy") bar = compile_from_file_input( - bar_input, output_formats=output_formats, input_bundle=input_bundle + bar_input, + output_formats=output_formats, + input_bundle=input_bundle, + storage_layout_override=BAR_STORAGE_LAYOUT_OVERRIDES, ) compile_code_results = { From d465568980b2505694b0da25adff36a2e8728672 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:16:04 +0800 Subject: [PATCH 38/38] fix merge conflict --- tests/unit/cli/vyper_compile/test_compile_files.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py index 007abb3512..a1f5ca098c 100644 --- a/tests/unit/cli/vyper_compile/test_compile_files.py +++ b/tests/unit/cli/vyper_compile/test_compile_files.py @@ -413,7 +413,7 @@ def test_archive_b64_output(input_files): def test_archive_compile_options(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file, _ = input_files search_paths = [".", tmpdir] options = ["abi_python", "json", "ast", "annotated_ast", "ir_json"] @@ -468,7 +468,7 @@ def test_archive_compile_options(input_files): def test_compile_vyz_with_options(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file, _ = input_files search_paths = [".", tmpdir] for option in format_options: @@ -497,7 +497,7 @@ def test_compile_vyz_with_options(input_files): def test_archive_compile_simultaneous_options(input_files): - tmpdir, _, _, contract_file = input_files + tmpdir, _, _, _, contract_file, _ = input_files search_paths = [".", tmpdir] for option in format_options: