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

refactor: internal handling of imports #3655

Merged
merged 63 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
8eaec10
add input bundle abstraction
charles-cooper Sep 26, 2023
6b47ee4
wip - input bundle
charles-cooper Sep 26, 2023
fd17ecb
wip - remove interface_codes
charles-cooper Sep 29, 2023
03a2ce6
remove dead code
charles-cooper Oct 20, 2023
0405874
wip
charles-cooper Oct 20, 2023
5d4d938
Merge branch 'master' into refactor/imports
charles-cooper Oct 20, 2023
5b22274
wip - thread input bundle through compiler
charles-cooper Oct 20, 2023
d8801c5
wip - more threading, update add_import helper
charles-cooper Oct 20, 2023
6d9695f
wip - search path as context manager
charles-cooper Oct 21, 2023
07eba74
small refactor
charles-cooper Oct 21, 2023
0fbf89c
refactor builtins
charles-cooper Oct 21, 2023
bf7f068
slight refactor of load_file
charles-cooper Oct 21, 2023
0ecfc5c
slight hygiene
charles-cooper Oct 21, 2023
37c25b1
move builtin interfaces to be `.vy` files
charles-cooper Oct 21, 2023
8af2795
fix abi input
charles-cooper Oct 21, 2023
32f11fd
wip - another fix
charles-cooper Oct 21, 2023
7c4a0e4
fix typo
charles-cooper Oct 21, 2023
662ca5f
wip - clean up compile_json.py
charles-cooper Oct 21, 2023
266c1dc
fix json output with paths
charles-cooper Oct 22, 2023
2f4f018
simplify keccak check
charles-cooper Oct 29, 2023
771061f
merge sources and interfaces for standard input json
charles-cooper Oct 29, 2023
28aefcb
refactor input_bundle.py
charles-cooper Oct 29, 2023
e78e81a
use input_bundle.load_file in vyper_compile.py
charles-cooper Oct 29, 2023
a2b21b7
clean up _resolve_import
charles-cooper Oct 29, 2023
38865ad
fix some typing/lint issues
charles-cooper Oct 29, 2023
bdfce82
rewrite builtin loading
charles-cooper Oct 31, 2023
ed98b0d
fix inheritance on JSONInputBundle
charles-cooper Oct 31, 2023
91487b9
fix error messages
charles-cooper Nov 1, 2023
159e10c
api fixes for json input
charles-cooper Nov 1, 2023
ccf6b74
fix lint
charles-cooper Nov 1, 2023
ee25e4f
wip - fix some mypy things
charles-cooper Nov 1, 2023
442f304
wip - remove compile_codes API
charles-cooper Nov 2, 2023
3f266cb
remove some mypy hints
charles-cooper Nov 2, 2023
36f974d
some fixes for vyper cli
charles-cooper Nov 2, 2023
76b980a
fix for standard json
charles-cooper Nov 2, 2023
0681a64
hygiene, use relpath for builtins
charles-cooper Nov 3, 2023
89ec6b8
formatting, notes
charles-cooper Nov 3, 2023
9124df3
fix some lint
charles-cooper Nov 3, 2023
44f8d74
fix some tests
charles-cooper Nov 3, 2023
7d25342
redo how ABI files are handled
charles-cooper Nov 3, 2023
306c63c
fix vyper_compile tests
charles-cooper Nov 3, 2023
d70eb01
remove interface_codes from compile_code calls
charles-cooper Nov 3, 2023
48d8cce
update json tests, remove code referencing get_interfaces
charles-cooper Nov 4, 2023
6669300
refactor input bundle json parsing
charles-cooper Nov 4, 2023
5cda829
fix a bunch of interface tests
charles-cooper Nov 4, 2023
08be231
remove a dead function
charles-cooper Nov 4, 2023
021bc07
fix a bunch of tests
charles-cooper Nov 5, 2023
8e96f3a
fix a bunch of tests
charles-cooper Nov 5, 2023
79ce767
fix some tests, mypy
charles-cooper Nov 5, 2023
6ba4260
fix some tests, lint
charles-cooper Nov 5, 2023
83a27e6
fix mypy
charles-cooper Nov 5, 2023
0a1cdff
fix some tests
charles-cooper Nov 5, 2023
f94412b
fix more tests
charles-cooper Nov 5, 2023
508a94b
rename a test file
charles-cooper Nov 5, 2023
e18de7b
add some tests for input bundles
charles-cooper Nov 5, 2023
fdfed5e
clean up vyper_compile tests
charles-cooper Nov 6, 2023
abe9f7d
simplify tests/cli/vyper_json
charles-cooper Nov 6, 2023
a96c53e
merge test_get_inputs.py and test_interfaces.py
charles-cooper Nov 6, 2023
25f8b29
clean up test_compile_json
charles-cooper Nov 6, 2023
f86977c
fix lint
charles-cooper Nov 6, 2023
8bb906c
fix a bad loop variable
charles-cooper Nov 6, 2023
a54363d
small cleanup
charles-cooper Nov 6, 2023
42ea90f
add some commentary
charles-cooper Nov 6, 2023
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
15 changes: 8 additions & 7 deletions tests/base_conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,18 @@ def w3(tester):
return w3


def _get_contract(w3, source_code, optimize, *args, override_opt_level=None, **kwargs):
def _get_contract(
w3, source_code, optimize, *args, override_opt_level=None, input_bundle=None, **kwargs
):
settings = Settings()
settings.evm_version = kwargs.pop("evm_version", None)
settings.optimize = override_opt_level or optimize
out = compiler.compile_code(
source_code,
# test that metadata and natspecs get generated
["abi", "bytecode", "metadata", "userdoc", "devdoc"],
output_formats=["abi", "bytecode", "metadata", "userdoc", "devdoc"],
settings=settings,
interface_codes=kwargs.pop("interface_codes", None),
input_bundle=input_bundle,
show_gas_estimates=True, # Enable gas estimates for testing
)
parse_vyper_source(source_code) # Test grammar.
Expand All @@ -144,8 +146,7 @@ def _deploy_blueprint_for(w3, source_code, optimize, initcode_prefix=b"", **kwar
settings.optimize = optimize
out = compiler.compile_code(
source_code,
["abi", "bytecode"],
interface_codes=kwargs.pop("interface_codes", None),
output_formats=["abi", "bytecode", "metadata", "userdoc", "devdoc"],
settings=settings,
show_gas_estimates=True, # Enable gas estimates for testing
)
Expand Down Expand Up @@ -187,10 +188,10 @@ def deploy_blueprint_for(source_code, *args, **kwargs):

@pytest.fixture(scope="module")
def get_contract(w3, optimize):
def get_contract(source_code, *args, **kwargs):
def fn(source_code, *args, **kwargs):
return _get_contract(w3, source_code, optimize, *args, **kwargs)

return get_contract
return fn


@pytest.fixture
Expand Down
205 changes: 198 additions & 7 deletions tests/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from pathlib import Path

import pytest

from vyper.cli.vyper_compile import compile_files


def test_combined_json_keys(tmp_path):
bar_path = tmp_path.joinpath("bar.vy")
with bar_path.open("w") as fp:
fp.write("")
def test_combined_json_keys(tmp_path, make_file):
make_file("bar.vy", "")

combined_keys = {
"bytecode",
Expand All @@ -19,12 +19,203 @@ def test_combined_json_keys(tmp_path):
"userdoc",
"devdoc",
}
compile_data = compile_files([bar_path], ["combined_json"], root_folder=tmp_path)
compile_data = compile_files(["bar.vy"], ["combined_json"], root_folder=tmp_path)

assert set(compile_data.keys()) == {"bar.vy", "version"}
assert set(compile_data["bar.vy"].keys()) == combined_keys
assert set(compile_data.keys()) == {Path("bar.vy"), "version"}
assert set(compile_data[Path("bar.vy")].keys()) == combined_keys


def test_invalid_root_path():
with pytest.raises(FileNotFoundError):
compile_files([], [], root_folder="path/that/does/not/exist")


FOO_CODE = """
{}

struct FooStruct:
foo_: uint256

@external
def foo() -> FooStruct:
return FooStruct({{foo_: 13}})

@external
def bar(a: address) -> FooStruct:
return {}(a).bar()
"""

BAR_CODE = """
struct FooStruct:
foo_: uint256
@external
def bar() -> FooStruct:
return FooStruct({foo_: 13})
"""


SAME_FOLDER_IMPORT_STMT = [
("import Bar as Bar", "Bar"),
("import contracts.Bar as Bar", "Bar"),
("from . import Bar", "Bar"),
("from contracts import Bar", "Bar"),
("from ..contracts import Bar", "Bar"),
("from . import Bar as FooBar", "FooBar"),
("from contracts import Bar as FooBar", "FooBar"),
("from ..contracts import Bar as FooBar", "FooBar"),
]


@pytest.mark.parametrize("import_stmt,alias", SAME_FOLDER_IMPORT_STMT)
def test_import_same_folder(import_stmt, alias, tmp_path, make_file):
foo = "contracts/foo.vy"
make_file("contracts/foo.vy", FOO_CODE.format(import_stmt, alias))
make_file("contracts/Bar.vy", BAR_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)


SUBFOLDER_IMPORT_STMT = [
("import other.Bar as Bar", "Bar"),
("import contracts.other.Bar as Bar", "Bar"),
("from other import Bar", "Bar"),
("from contracts.other import Bar", "Bar"),
("from .other import Bar", "Bar"),
("from ..contracts.other import Bar", "Bar"),
("from other import Bar as FooBar", "FooBar"),
("from contracts.other import Bar as FooBar", "FooBar"),
("from .other import Bar as FooBar", "FooBar"),
("from ..contracts.other import Bar as FooBar", "FooBar"),
]


@pytest.mark.parametrize("import_stmt, alias", SUBFOLDER_IMPORT_STMT)
def test_import_subfolder(import_stmt, alias, tmp_path, make_file):
foo = make_file("contracts/foo.vy", (FOO_CODE.format(import_stmt, alias)))
make_file("contracts/other/Bar.vy", BAR_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)


OTHER_FOLDER_IMPORT_STMT = [
("import interfaces.Bar as Bar", "Bar"),
("from interfaces import Bar", "Bar"),
("from ..interfaces import Bar", "Bar"),
("from interfaces import Bar as FooBar", "FooBar"),
("from ..interfaces import Bar as FooBar", "FooBar"),
]


@pytest.mark.parametrize("import_stmt, alias", OTHER_FOLDER_IMPORT_STMT)
def test_import_other_folder(import_stmt, alias, tmp_path, make_file):
foo = make_file("contracts/foo.vy", FOO_CODE.format(import_stmt, alias))
make_file("interfaces/Bar.vy", BAR_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)


def test_import_parent_folder(tmp_path, make_file):
foo = make_file("contracts/baz/foo.vy", FOO_CODE.format("from ... import Bar", "Bar"))
make_file("Bar.vy", BAR_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)

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


META_IMPORT_STMT = [
"import Meta as Meta",
"import contracts.Meta as Meta",
"from . import Meta",
"from contracts import Meta",
]


@pytest.mark.parametrize("import_stmt", META_IMPORT_STMT)
def test_import_self_interface(import_stmt, tmp_path, make_file):
# a contract can access its derived interface by importing itself
code = f"""
{import_stmt}

struct FooStruct:
foo_: uint256

@external
def know_thyself(a: address) -> FooStruct:
return Meta(a).be_known()

@external
def be_known() -> FooStruct:
return FooStruct({{foo_: 42}})
"""
meta = make_file("contracts/Meta.vy", code)

assert compile_files([meta], ["combined_json"], root_folder=tmp_path)


DERIVED_IMPORT_STMT_BAZ = ["import Foo as Foo", "from . import Foo"]

DERIVED_IMPORT_STMT_FOO = ["import Bar as Bar", "from . import Bar"]


@pytest.mark.parametrize("import_stmt_baz", DERIVED_IMPORT_STMT_BAZ)
@pytest.mark.parametrize("import_stmt_foo", DERIVED_IMPORT_STMT_FOO)
def test_derived_interface_imports(import_stmt_baz, import_stmt_foo, tmp_path, make_file):
# contracts-as-interfaces should be able to contain import statements
baz_code = f"""
{import_stmt_baz}

struct FooStruct:
foo_: uint256

@external
def foo(a: address) -> FooStruct:
return Foo(a).foo()

@external
def bar(_foo: address, _bar: address) -> FooStruct:
return Foo(_foo).bar(_bar)
"""

make_file("Foo.vy", FOO_CODE.format(import_stmt_foo, "Bar"))
make_file("Bar.vy", BAR_CODE)
baz = make_file("Baz.vy", baz_code)

assert compile_files([baz], ["combined_json"], root_folder=tmp_path)


def test_local_namespace(make_file, tmp_path):
# interface code namespaces should be isolated
# all of these contract should be able to compile together
codes = [
"import foo as FooBar",
"import bar as FooBar",
"import foo as BarFoo",
"import bar as BarFoo",
]
struct_def = """
struct FooStruct:
foo_: uint256

"""

paths = []
for i, code in enumerate(codes):
code += struct_def
filename = f"code{i}.vy"
make_file(filename, code)
paths.append(filename)

for file_name in ("foo.vy", "bar.vy"):
make_file(file_name, BAR_CODE)

assert compile_files(paths, ["combined_json"], root_folder=tmp_path)


def test_compile_outside_root_path(tmp_path, make_file):
# absolute paths relative to "."
foo = make_file("foo.vy", FOO_CODE.format("import bar as Bar", "Bar"))
bar = make_file("bar.vy", BAR_CODE)

assert compile_files([foo, bar], ["combined_json"], root_folder=".")
Loading