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[tool]: archive format #3891

Merged
merged 73 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
890902b
feat[tool]: add archive format for vyper
charles-cooper Mar 24, 2024
93ab6df
filter search paths
charles-cooper Mar 24, 2024
7040a8a
add compression
charles-cooper Mar 24, 2024
7090cbc
fix a function signature
charles-cooper Mar 24, 2024
4ae2f01
use zlib compression format
charles-cooper Mar 25, 2024
800cdee
sort search paths by precedence
charles-cooper Mar 25, 2024
d634a1c
filter out builtins
charles-cooper Mar 25, 2024
ee84cad
add a note
charles-cooper Mar 25, 2024
1ea2308
add integrity output type, add integrity to archive mode
charles-cooper Mar 25, 2024
895f3aa
save original settings
charles-cooper Mar 25, 2024
cc54d46
add compiler settings to manifest
charles-cooper Mar 25, 2024
6d7a89d
add sentinel for when no settings are provided
charles-cooper Mar 25, 2024
a8ef5de
update some tests
charles-cooper Mar 25, 2024
7694cac
Merge branch 'master' into feat/archives
charles-cooper Apr 3, 2024
5f242af
Merge branch 'master' into feat/archives
charles-cooper Apr 22, 2024
9be0ba2
fix lint
charles-cooper Apr 22, 2024
3e2e8d0
add --disable-sys-path flag
charles-cooper Apr 22, 2024
85eca0d
feat: anonymize files in the output archive
charles-cooper Apr 3, 2024
f26d24b
use asdict
charles-cooper Apr 23, 2024
6883442
add note
charles-cooper Apr 25, 2024
e0ebf63
add solc json compatibility mode
charles-cooper Apr 25, 2024
d35e1a2
refactor - output bundle abstraction
charles-cooper Apr 25, 2024
04eec5c
write zipfile bytes
charles-cooper Apr 26, 2024
9560424
compile from zipfile input(!)
charles-cooper Apr 27, 2024
e96cbb0
add missing file
charles-cooper Apr 27, 2024
d7ecd8b
add a note
charles-cooper Apr 27, 2024
c2190f3
add another note
charles-cooper Apr 27, 2024
11a0315
add base64 output
charles-cooper Apr 28, 2024
8ecd4a2
fix lint
charles-cooper Apr 28, 2024
1cd7e22
merge settings from archive and cli
charles-cooper Apr 28, 2024
d9b0a38
only allow single output for solc_json and archive output formats
charles-cooper Apr 28, 2024
7bcb943
add version to output bundle
charles-cooper Apr 28, 2024
bf5d3c9
Merge branch 'master' into feat/archives
charles-cooper Apr 28, 2024
faa6aec
remove todo
charles-cooper Apr 28, 2024
b9216a2
remove another todo
charles-cooper Apr 28, 2024
e355a19
fix a comment
charles-cooper Apr 28, 2024
4543921
serialize more settings
charles-cooper Apr 28, 2024
274dc62
fix a missing paren
charles-cooper Apr 28, 2024
19c302f
update some tests
charles-cooper Apr 30, 2024
168903e
fix some tests
charles-cooper Apr 30, 2024
12302e8
Merge branch 'master' into feat/archives
charles-cooper Apr 30, 2024
5dec9cc
add a test for archive mode
charles-cooper Apr 30, 2024
c51500f
test archive_b64
charles-cooper Apr 30, 2024
b2bb3db
test solc json output
charles-cooper Apr 30, 2024
63bf65c
test for integrity sum
charles-cooper Apr 30, 2024
cca296a
make get_long_version utility
charles-cooper May 1, 2024
b1e3827
convert some tests to check is not None
charles-cooper May 1, 2024
2298e7d
add a comment
charles-cooper May 1, 2024
9ed46b8
Merge branch 'master' into feat/archives
charles-cooper May 1, 2024
9e6c048
Merge branch 'master' into feat/archives
charles-cooper May 2, 2024
df62b1e
normalize search path
charles-cooper May 2, 2024
1948999
try again
charles-cooper May 2, 2024
f9cd8a8
catch some exceptions
charles-cooper May 2, 2024
fca082b
add a sanity check
charles-cooper May 2, 2024
a21dbe1
add another sanity check
charles-cooper May 2, 2024
31edd11
formatting
charles-cooper May 5, 2024
957d629
update a comment
charles-cooper May 5, 2024
5d0533b
request bytecode in build_archive and build_solc_json
charles-cooper May 5, 2024
d754b02
fix output_formats fixture
charles-cooper May 5, 2024
3c8a0ed
manually order destruction of zipfile child
charles-cooper May 5, 2024
edcbdfa
comment
charles-cooper May 5, 2024
22f0688
Merge branch 'master' into feat/archives
charles-cooper May 6, 2024
6f59a63
add integrity sum check
charles-cooper May 6, 2024
bb63e49
warn on bad compilation instead of erroring out
charles-cooper May 6, 2024
658f6ff
Merge branch 'master' into feat/archives
charles-cooper May 6, 2024
9a8ea77
add another test for used search paths computation
charles-cooper May 6, 2024
f8033fb
Merge branch 'master' into feat/archives
charles-cooper May 7, 2024
8b635b6
fix settings from_dict
charles-cooper May 7, 2024
9f9d295
add json example to archiver
charles-cooper May 7, 2024
47376b7
fix evm_version in solc_json settings
charles-cooper May 7, 2024
c349e42
remove binary comparison between zipfiles
charles-cooper May 7, 2024
c18f689
fix solc json: experimentalCodegen in output bundle
charles-cooper May 7, 2024
6315ec1
Merge branch 'master' into feat/archives
charles-cooper May 7, 2024
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
9 changes: 5 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ def pytest_addoption(parser):
@pytest.fixture(scope="module")
def output_formats():
output_formats = compiler.OUTPUT_FORMATS.copy()
del output_formats["bb"]
del output_formats["bb_runtime"]
del output_formats["cfg"]
del output_formats["cfg_runtime"]

to_drop = ("bb", "bb_runtime", "cfg", "cfg_runtime", "archive", "archive_b64", "solc_json")
for s in to_drop:
del output_formats[s]

return output_formats


Expand Down
182 changes: 158 additions & 24 deletions tests/unit/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import contextlib
import sys
import zipfile
from pathlib import Path

import pytest

from tests.utils import working_directory
from vyper.cli.vyper_compile import compile_files
from vyper.cli.vyper_json import 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


def test_combined_json_keys(tmp_path, make_file):
def test_combined_json_keys(chdir_tmp_path, make_file):
make_file("bar.vy", "")

combined_keys = {
Expand All @@ -22,7 +27,7 @@ def test_combined_json_keys(tmp_path, make_file):
"userdoc",
"devdoc",
}
compile_data = compile_files(["bar.vy"], ["combined_json"], paths=[tmp_path])
compile_data = compile_files(["bar.vy"], ["combined_json"])

assert set(compile_data.keys()) == {Path("bar.vy"), "version"}
assert set(compile_data[Path("bar.vy")].keys()) == combined_keys
Expand Down Expand Up @@ -72,12 +77,12 @@ def bar() -> FooStruct:


@pytest.mark.parametrize("import_stmt,alias", SAME_FOLDER_IMPORT_STMT)
def test_import_same_folder(import_stmt, alias, tmp_path, make_file):
def test_import_same_folder(import_stmt, alias, chdir_tmp_path, make_file):
foo = "contracts/foo.vy"
make_file("contracts/foo.vy", CONTRACT_CODE.format(import_stmt=import_stmt, alias=alias))
make_file("contracts/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], paths=[tmp_path])
assert compile_files([foo], ["combined_json"]) is not None


SUBFOLDER_IMPORT_STMT = [
Expand All @@ -95,13 +100,13 @@ def test_import_same_folder(import_stmt, alias, tmp_path, make_file):


@pytest.mark.parametrize("import_stmt, alias", SUBFOLDER_IMPORT_STMT)
def test_import_subfolder(import_stmt, alias, tmp_path, make_file):
def test_import_subfolder(import_stmt, alias, chdir_tmp_path, make_file):
foo = make_file(
"contracts/foo.vy", (CONTRACT_CODE.format(import_stmt=import_stmt, alias=alias))
)
make_file("contracts/other/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], paths=[tmp_path])
assert compile_files([foo], ["combined_json"]) is not None


OTHER_FOLDER_IMPORT_STMT = [
Expand All @@ -118,7 +123,7 @@ def test_import_other_folder(import_stmt, alias, tmp_path, make_file):
foo = make_file("contracts/foo.vy", CONTRACT_CODE.format(import_stmt=import_stmt, alias=alias))
make_file("interfaces/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], paths=[tmp_path])
assert compile_files([foo], ["combined_json"], paths=[tmp_path]) is not None


def test_import_parent_folder(tmp_path, make_file):
Expand All @@ -128,21 +133,20 @@ def test_import_parent_folder(tmp_path, make_file):
)
make_file("IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], paths=[tmp_path])
assert compile_files([foo], ["combined_json"], paths=[tmp_path]) is not None

# perform relative import outside of base folder
compile_files([foo], ["combined_json"], paths=[tmp_path / "contracts"])


def test_import_search_paths(tmp_path, make_file):
with working_directory(tmp_path):
contract_code = CONTRACT_CODE.format(import_stmt="from utils import IFoo", alias="IFoo")
contract_filename = "dir1/baz/foo.vy"
interface_filename = "dir2/utils/IFoo.vyi"
make_file(interface_filename, INTERFACE_CODE)
make_file(contract_filename, contract_code)
def test_import_search_paths(chdir_tmp_path, make_file):
contract_code = CONTRACT_CODE.format(import_stmt="from utils import IFoo", alias="IFoo")
contract_filename = "dir1/baz/foo.vy"
interface_filename = "dir2/utils/IFoo.vyi"
make_file(interface_filename, INTERFACE_CODE)
make_file(contract_filename, contract_code)

assert compile_files([contract_filename], ["combined_json"], paths=["dir2"])
assert compile_files([contract_filename], ["combined_json"], paths=["dir2"]) is not None


META_IMPORT_STMT = [
Expand Down Expand Up @@ -181,7 +185,7 @@ def be_known() -> ISelf.FooStruct:
make_file("contracts/ISelf.vyi", interface_code)
meta = make_file("contracts/Self.vy", code)

assert compile_files([meta], ["combined_json"], paths=[tmp_path])
assert compile_files([meta], ["combined_json"], paths=[tmp_path]) is not None


# implement IFoo in another contract for fun
Expand All @@ -201,10 +205,10 @@ def bar(_foo: address) -> {alias}.FooStruct:
make_file("contracts/IFoo.vyi", INTERFACE_CODE)
baz = make_file("contracts/Baz.vy", baz_code)

assert compile_files([baz], ["combined_json"], paths=[tmp_path])
assert compile_files([baz], ["combined_json"], paths=[tmp_path]) is not None


def test_local_namespace(make_file, tmp_path):
def test_local_namespace(make_file, chdir_tmp_path):
# interface code namespaces should be isolated
# all of these contract should be able to compile together
codes = [
Expand All @@ -229,15 +233,15 @@ def test_local_namespace(make_file, tmp_path):
for file_name in ("foo.vyi", "bar.vyi"):
make_file(file_name, INTERFACE_CODE)

assert compile_files(paths, ["combined_json"], paths=[tmp_path])
assert compile_files(paths, ["combined_json"]) is not None


def test_compile_outside_root_path(tmp_path, make_file):
# absolute paths relative to "."
make_file("ifoo.vyi", INTERFACE_CODE)
foo = make_file("foo.vy", CONTRACT_CODE.format(import_stmt="import ifoo as IFoo", alias="IFoo"))

assert compile_files([foo], ["combined_json"], paths=None)
assert compile_files([foo], ["combined_json"], paths=None) is not None


def test_import_library(tmp_path, make_file):
Expand Down Expand Up @@ -270,23 +274,153 @@ def mock_sys_path(path):
sys.path.pop()


def test_import_sys_path(tmp_path_factory, make_file):
@pytest.fixture
def input_files(tmp_path_factory, make_file, chdir_tmp_path):
library_source = """
@internal
def foo() -> uint256:
return block.number + 1
"""
json_source = """
[
{
"stateMutability": "nonpayable",
"type": "function",
"name": "test_json",
"inputs": [ { "name": "", "type": "uint256" } ],
"outputs": [ { "name": "", "type": "uint256" } ]
}
]
"""
contract_source = """
import lib
import jsonabi

@external
def foo() -> uint256:
return lib.foo()

@external
def bar(x: uint256) -> uint256:
return extcall jsonabi(msg.sender).test_json(x)
"""
tmpdir = tmp_path_factory.mktemp("test-sys-path")
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)

contract_file = make_file("contract.vy", contract_source)

return (tmpdir, tmpdir / "lib.vy", tmpdir / "jsonabi.json", contract_file)


def test_import_sys_path(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
search_paths = [".", tmpdir]

s = compile_files([contract_file], ["archive"], paths=search_paths)
archive_bytes = s[contract_file]["archive"]

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

assert zipfile.is_zipfile(archive_path)

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


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

out = compile_files(
[contract_file], ["archive_b64", "integrity", "bytecode"], 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], ["integrity", "bytecode"])
assert out[contract_file] == out2[archive_path]


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

out = compile_files([contract_file], ["solc_json"], paths=search_paths)

json_input = out[contract_file]["solc_json"]

# check that round-tripping solc_json thru standard json produces
# the same as compiling directly
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)

assert out2[contract_file]["bytecode"] == json_out_bytecode


# maybe this belongs in tests/unit/compiler?
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
def test_integrity_sum(input_files):
tmpdir, library_file, jsonabi_file, contract_file = input_files
search_paths = [".", tmpdir]

out = compile_files([contract_file], ["integrity"], paths=search_paths)

with library_file.open() as f, contract_file.open() as g, jsonabi_file.open() as h:
library_contents = f.read()
contract_contents = g.read()
jsonabi_contents = h.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)
assert out[contract_file]["integrity"] == expected


# does this belong in tests/unit/compiler?
def test_archive_search_path(tmp_path_factory, make_file, chdir_tmp_path):
lib1 = """
x: uint256
"""
lib2 = """
y: uint256
"""
dir1 = tmp_path_factory.mktemp("dir1")
dir2 = tmp_path_factory.mktemp("dir2")
make_file(dir1 / "lib.vy", lib1)
make_file(dir2 / "lib.vy", lib2)

main = """
import lib
"""
pwd = Path(".")
make_file(pwd / "main.vy", main)
for search_paths in ([pwd, dir1, dir2], [pwd, dir2, dir1]):
input_bundle = FilesystemInputBundle(search_paths)
file_input = input_bundle.load_file("main.vy")

# construct CompilerData manually
compiler_data = CompilerData(file_input, input_bundle)
output_bundle = OutputBundle(compiler_data)

used_dir = search_paths[-1].stem # either dir1 or dir2
assert output_bundle.used_search_paths == [".", "0/" + used_dir]
5 changes: 0 additions & 5 deletions tests/unit/cli/vyper_json/test_compile_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,6 @@ def test_different_outputs(input_bundle, input_json):
assert foo["evm"]["methodIdentifiers"] == method_identifiers


def test_root_folder_not_exists(input_json):
with pytest.raises(FileNotFoundError):
compile_json(input_json, root_folder="/path/that/does/not/exist")


def test_wrong_language():
with pytest.raises(JSONError):
compile_json({"language": "Solidity"})
Expand Down
21 changes: 12 additions & 9 deletions tests/unit/compiler/test_input_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ def test_load_abi(make_file, input_bundle, tmp_path):

file = input_bundle.load_file("foo.json")
assert isinstance(file, ABIInput)
assert file == ABIInput(0, "foo.json", path, "some string")
assert file == ABIInput(0, "foo.json", path, contents, "some string")

# suffix doesn't matter
path = make_file("foo.txt", contents)
file = input_bundle.load_file("foo.txt")
assert isinstance(file, ABIInput)
assert file == ABIInput(1, "foo.txt", path, "some string")
assert file == ABIInput(1, "foo.txt", path, contents, "some string")


# check that unique paths give unique source ids
Expand Down Expand Up @@ -126,29 +126,31 @@ def test_source_id_json_input(make_file, input_bundle, tmp_path):

file = input_bundle.load_file("foo.json")
assert isinstance(file, ABIInput)
assert file == ABIInput(0, "foo.json", foopath, "some string")
assert file == ABIInput(0, "foo.json", foopath, contents, "some string")

file2 = input_bundle.load_file("bar.json")
assert isinstance(file2, ABIInput)
assert file2 == ABIInput(1, "bar.json", barpath, ["some list"])
assert file2 == ABIInput(1, "bar.json", barpath, contents2, ["some list"])

file3 = input_bundle.load_file("foo.json")
assert file3.source_id == 0
assert file3 == ABIInput(0, "foo.json", foopath, "some string")
assert file3 == ABIInput(0, "foo.json", foopath, contents, "some string")

# test source id is stable across different search paths
with working_directory(tmp_path):
with input_bundle.search_path(Path(".")):
file4 = input_bundle.load_file("foo.json")
assert file4.source_id == 0
assert file4 == ABIInput(0, "foo.json", foopath, "some string")
assert file4 == ABIInput(0, "foo.json", foopath, contents, "some string")

# test source id is stable even when requested filename is different
with working_directory(tmp_path.parent):
with input_bundle.search_path(Path(".")):
file5 = input_bundle.load_file(Path(tmp_path.stem) / "foo.json")
assert file5.source_id == 0
assert file5 == ABIInput(0, Path(tmp_path.stem) / "foo.json", foopath, "some string")
assert file5 == ABIInput(
0, Path(tmp_path.stem) / "foo.json", foopath, contents, "some string"
)


# test some pathological case where the file changes underneath
Expand Down Expand Up @@ -238,7 +240,8 @@ def test_json_input_abi():
input_bundle = JSONInputBundle(files, [PurePath(".")])

file = input_bundle.load_file(foopath)
assert file == ABIInput(0, foopath, foopath, some_abi)
abi_contents = json.dumps({"abi": some_abi})
assert file == ABIInput(0, foopath, foopath, abi_contents, some_abi)

file = input_bundle.load_file(barpath)
assert file == ABIInput(1, barpath, barpath, some_abi)
assert file == ABIInput(1, barpath, barpath, some_abi_str, some_abi)
Loading
Loading