Skip to content

Commit 0b452c8

Browse files
Merge branch 'master' into fix/ierc4626
2 parents 3a89b6a + 705aa54 commit 0b452c8

File tree

11 files changed

+179
-7
lines changed

11 files changed

+179
-7
lines changed

tests/functional/codegen/test_interfaces.py

+105
Original file line numberDiff line numberDiff line change
@@ -876,3 +876,108 @@ def bar() -> uint256:
876876
input_bundle = make_input_bundle({"lib1.vy": lib1})
877877
c = get_contract(main, input_bundle=input_bundle)
878878
assert c.bar() == 1
879+
880+
881+
def test_interface_with_flags():
882+
code = """
883+
struct MyStruct:
884+
a: address
885+
886+
flag Foo:
887+
BOO
888+
MOO
889+
POO
890+
891+
event Transfer:
892+
sender: indexed(address)
893+
894+
@external
895+
def bar():
896+
pass
897+
flag BAR:
898+
BIZ
899+
BAZ
900+
BOO
901+
902+
@external
903+
@view
904+
def foo(s: MyStruct) -> MyStruct:
905+
return s
906+
"""
907+
908+
out = compile_code(code, contract_path="code.vy", output_formats=["interface"])["interface"]
909+
910+
assert "# Flags" in out
911+
assert "flag Foo:" in out
912+
assert "flag BAR" in out
913+
assert "BOO" in out
914+
assert "MOO" in out
915+
916+
compile_code(out, contract_path="code.vyi", output_formats=["interface"])
917+
918+
919+
vyi_filenames = [
920+
"test__test.vyi",
921+
"test__t.vyi",
922+
"t__test.vyi",
923+
"t__t.vyi",
924+
"t_t.vyi",
925+
"test_test.vyi",
926+
"t_test.vyi",
927+
"test_t.vyi",
928+
"_test_t__t_tt_.vyi",
929+
"foo_bar_baz.vyi",
930+
]
931+
932+
933+
@pytest.mark.parametrize("vyi_filename", vyi_filenames)
934+
def test_external_interface_names(vyi_filename):
935+
code = """
936+
@external
937+
def foo():
938+
...
939+
"""
940+
941+
compile_code(code, contract_path=vyi_filename, output_formats=["external_interface"])
942+
943+
944+
def test_external_interface_with_flag():
945+
code = """
946+
flag Foo:
947+
Blah
948+
949+
@external
950+
def foo() -> Foo:
951+
...
952+
"""
953+
954+
out = compile_code(code, contract_path="test__test.vyi", output_formats=["external_interface"])[
955+
"external_interface"
956+
]
957+
assert "-> Foo:" in out
958+
959+
960+
def test_external_interface_compiles_again():
961+
code = """
962+
@external
963+
def foo() -> uint256:
964+
...
965+
@external
966+
def bar(a:int32) -> uint256:
967+
...
968+
"""
969+
970+
out = compile_code(code, contract_path="test.vyi", output_formats=["external_interface"])[
971+
"external_interface"
972+
]
973+
compile_code(out, contract_path="test.vyi", output_formats=["external_interface"])
974+
975+
976+
@pytest.mark.xfail
977+
def test_weird_interface_name():
978+
# based on comment https://github.com/vyperlang/vyper/pull/4290#discussion_r1884137428
979+
# we replace "_" for "" which results in an interface without name
980+
out = compile_code("", contract_path="_.vyi", output_formats=["external_interface"])[
981+
"external_interface"
982+
]
983+
assert "interface _:" in out

tests/unit/ast/test_ast_dict.py

+2
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ def foo():
399399
"node_id": 0,
400400
"path": "main.vy",
401401
"source_id": 1,
402+
"is_interface": False,
402403
"type": {
403404
"name": "main.vy",
404405
"type_decl_node": {"node_id": 0, "source_id": 1},
@@ -1175,6 +1176,7 @@ def foo():
11751176
"node_id": 0,
11761177
"path": "lib1.vy",
11771178
"source_id": 0,
1179+
"is_interface": False,
11781180
"type": {
11791181
"name": "lib1.vy",
11801182
"type_decl_node": {"node_id": 0, "source_id": 0},

tests/unit/cli/vyper_compile/test_compile_files.py

+29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from vyper.cli.vyper_compile import compile_files
99
from vyper.cli.vyper_json import compile_json
10+
from vyper.compiler import INTERFACE_OUTPUT_FORMATS, OUTPUT_FORMATS
1011
from vyper.compiler.input_bundle import FilesystemInputBundle
1112
from vyper.compiler.output_bundle import OutputBundle
1213
from vyper.compiler.phases import CompilerData
@@ -425,3 +426,31 @@ def test_archive_search_path(tmp_path_factory, make_file, chdir_tmp_path):
425426

426427
used_dir = search_paths[-1].stem # either dir1 or dir2
427428
assert output_bundle.used_search_paths == [".", "0/" + used_dir]
429+
430+
431+
def test_compile_interface_file(make_file):
432+
interface = """
433+
@view
434+
@external
435+
def foo() -> String[1]:
436+
...
437+
438+
@view
439+
@external
440+
def bar() -> String[1]:
441+
...
442+
443+
@external
444+
def baz() -> uint8:
445+
...
446+
447+
"""
448+
file = make_file("interface.vyi", interface)
449+
compile_files([file], INTERFACE_OUTPUT_FORMATS)
450+
451+
# check unallowed output formats
452+
for f in OUTPUT_FORMATS:
453+
if f in INTERFACE_OUTPUT_FORMATS:
454+
continue
455+
with pytest.raises(ValueError):
456+
compile_files([file], [f])

vyper/ast/nodes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ class TopLevel(VyperNode):
638638

639639
class Module(TopLevel):
640640
# metadata
641-
__slots__ = ("path", "resolved_path", "source_id")
641+
__slots__ = ("path", "resolved_path", "source_id", "is_interface")
642642

643643
def to_dict(self):
644644
return dict(source_sha256sum=self.source_sha256sum, **super().to_dict())

vyper/ast/nodes.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Module(TopLevel):
7171
path: str = ...
7272
resolved_path: str = ...
7373
source_id: int = ...
74+
is_interface: bool = ...
7475
def namespace(self) -> Any: ... # context manager
7576

7677
class FunctionDef(TopLevel):

vyper/ast/parse.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ def parse_to_ast_with_settings(
2323
module_path: Optional[str] = None,
2424
resolved_path: Optional[str] = None,
2525
add_fn_node: Optional[str] = None,
26+
is_interface: bool = False,
2627
) -> tuple[Settings, vy_ast.Module]:
2728
try:
2829
return _parse_to_ast_with_settings(
29-
vyper_source, source_id, module_path, resolved_path, add_fn_node
30+
vyper_source, source_id, module_path, resolved_path, add_fn_node, is_interface
3031
)
3132
except SyntaxException as e:
3233
e.resolved_path = resolved_path
@@ -39,6 +40,7 @@ def _parse_to_ast_with_settings(
3940
module_path: Optional[str] = None,
4041
resolved_path: Optional[str] = None,
4142
add_fn_node: Optional[str] = None,
43+
is_interface: bool = False,
4244
) -> tuple[Settings, vy_ast.Module]:
4345
"""
4446
Parses a Vyper source string and generates basic Vyper AST nodes.
@@ -62,6 +64,9 @@ def _parse_to_ast_with_settings(
6264
resolved_path: str, optional
6365
The resolved path of the source code
6466
Corresponds to FileInput.resolved_path
67+
is_interface: bool
68+
Indicates whether the source code should
69+
be parsed as an interface file.
6570
6671
Returns
6772
-------
@@ -106,6 +111,7 @@ def _parse_to_ast_with_settings(
106111
# Convert to Vyper AST.
107112
module = vy_ast.get_node(py_ast)
108113
assert isinstance(module, vy_ast.Module) # mypy hint
114+
module.is_interface = is_interface
109115

110116
return pre_parser.settings, module
111117

vyper/compiler/__init__.py

+15
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
"opcodes_runtime": output.build_opcodes_runtime_output,
4747
}
4848

49+
INTERFACE_OUTPUT_FORMATS = [
50+
"ast_dict",
51+
"annotated_ast_dict",
52+
"interface",
53+
"external_interface",
54+
"abi",
55+
]
4956

5057
UNKNOWN_CONTRACT_NAME = "<unknown>"
5158

@@ -121,10 +128,18 @@ def outputs_from_compiler_data(
121128
output_formats = ("bytecode",)
122129

123130
ret = {}
131+
124132
with anchor_settings(compiler_data.settings):
125133
for output_format in output_formats:
126134
if output_format not in OUTPUT_FORMATS:
127135
raise ValueError(f"Unsupported format type {repr(output_format)}")
136+
137+
is_vyi = compiler_data.file_input.resolved_path.suffix == ".vyi"
138+
if is_vyi and output_format not in INTERFACE_OUTPUT_FORMATS:
139+
raise ValueError(
140+
f"Unsupported format for compiling interface: {repr(output_format)}"
141+
)
142+
128143
try:
129144
formatter = OUTPUT_FORMATS[output_format]
130145
ret[output_format] = formatter(compiler_data)

vyper/compiler/output.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,8 @@ def build_integrity(compiler_data: CompilerData) -> str:
108108
def build_external_interface_output(compiler_data: CompilerData) -> str:
109109
interface = compiler_data.annotated_vyper_module._metadata["type"].interface
110110
stem = PurePath(compiler_data.contract_path).stem
111-
# capitalize words separated by '_'
112-
# ex: test_interface.vy -> TestInterface
113-
name = "".join([x.capitalize() for x in stem.split("_")])
111+
112+
name = stem.title().replace("_", "")
114113
out = f"\n# External Interfaces\ninterface {name}:\n"
115114

116115
for func in interface.functions.values():
@@ -136,6 +135,14 @@ def build_interface_output(compiler_data: CompilerData) -> str:
136135
out += f" {member_name}: {member_type}\n"
137136
out += "\n\n"
138137

138+
if len(interface.flags) > 0:
139+
out += "# Flags\n\n"
140+
for flag in interface.flags.values():
141+
out += f"flag {flag.name}:\n"
142+
for flag_value in flag._flag_members:
143+
out += f" {flag_value}\n"
144+
out += "\n\n"
145+
139146
if len(interface.events) > 0:
140147
out += "# Events\n\n"
141148
for event in interface.events.values():
@@ -282,7 +289,8 @@ def build_method_identifiers_output(compiler_data: CompilerData) -> dict:
282289

283290
def build_abi_output(compiler_data: CompilerData) -> list:
284291
module_t = compiler_data.annotated_vyper_module._metadata["type"]
285-
_ = compiler_data.ir_runtime # ensure _ir_info is generated
292+
if not compiler_data.annotated_vyper_module.is_interface:
293+
_ = compiler_data.ir_runtime # ensure _ir_info is generated
286294

287295
abi = module_t.interface.to_toplevel_abi_dict()
288296
if module_t.init_function:

vyper/compiler/phases.py

+3
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,14 @@ def contract_path(self):
113113

114114
@cached_property
115115
def _generate_ast(self):
116+
is_vyi = self.contract_path.suffix == ".vyi"
117+
116118
settings, ast = vy_ast.parse_to_ast_with_settings(
117119
self.source_code,
118120
self.source_id,
119121
module_path=self.contract_path.as_posix(),
120122
resolved_path=self.file_input.resolved_path.as_posix(),
123+
is_interface=is_vyi,
121124
)
122125

123126
if self.original_settings:

vyper/semantics/analysis/module.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def analyze_module(module_ast: vy_ast.Module) -> ModuleT:
5353
add all module-level objects to the namespace, type-check/validate
5454
semantics and annotate with type and analysis info
5555
"""
56-
return _analyze_module_r(module_ast)
56+
return _analyze_module_r(module_ast, module_ast.is_interface)
5757

5858

5959
def _analyze_module_r(module_ast: vy_ast.Module, is_interface: bool = False):

vyper/semantics/types/user.py

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ def get_type_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType":
7777
self._helper.get_member(key, node)
7878
return self
7979

80+
def __str__(self):
81+
return f"{self.name}"
82+
8083
def __repr__(self):
8184
arg_types = ",".join(repr(a) for a in self._flag_members)
8285
return f"flag {self.name}({arg_types})"

0 commit comments

Comments
 (0)