Skip to content

Commit 208eee6

Browse files
committed
Merge branch 'master' into feat/venom/call_conv_stack_return
2 parents fbd7108 + cb8a732 commit 208eee6

File tree

10 files changed

+441
-35
lines changed

10 files changed

+441
-35
lines changed

docs/interfaces.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ You can define an interface for your contract with the ``implements`` statement:
112112
113113
This imports the defined interface from the vyper file at ``an_interface.vyi`` (or ``an_interface.json`` if using ABI json interface type) and ensures your current contract implements all the necessary external functions. If any interface functions are not included in the contract, it will fail to compile. This is especially useful when developing contracts around well-defined standards such as ERC20.
114114

115+
Multiple ``implements`` statements can be grouped into one:
116+
117+
.. code-block:: vyper
118+
119+
implements: Foo
120+
implements: Bar
121+
122+
# Equivalent to:
123+
124+
implements: (
125+
Foo,
126+
Bar,
127+
)
128+
115129
.. note::
116130

117131
Interfaces that implement functions with return values that require an upper bound (e.g. ``Bytes``, ``DynArray``, or ``String``), the upper bound defined in the interface represents the lower bound of the implementation. Assuming a function ``my_func`` returns a value ``String[1]`` in the interface, this would mean for the implementation function of ``my_func`` that the return value must have **at least** length 1. This behavior might change in the future.

tests/functional/syntax/modules/test_implements.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import pytest
2+
13
from vyper.compiler import compile_code
4+
from vyper.exceptions import StructureException
25

36

47
def test_implements_from_vyi(make_input_bundle):
@@ -49,3 +52,95 @@ def foo(): # implementation
4952
input_bundle = make_input_bundle({"some_interface.vyi": vyi, "lib1.vy": lib1, "lib2.vy": lib2})
5053

5154
assert compile_code(main, input_bundle=input_bundle) is not None
55+
56+
57+
def test_duplicate_implements_in_same_statement_fails(make_input_bundle):
58+
vyi = """
59+
@external
60+
def foo():
61+
...
62+
"""
63+
main = """
64+
import some_interface
65+
66+
implements: (
67+
some_interface,
68+
some_interface,
69+
)
70+
71+
@external
72+
def foo(): # implementation
73+
pass
74+
"""
75+
input_bundle = make_input_bundle({"some_interface.vyi": vyi})
76+
77+
with pytest.raises(StructureException) as e:
78+
compile_code(main, input_bundle=input_bundle)
79+
80+
assert e.value._message == "some_interface implemented more than once"
81+
assert e.value._hint is None
82+
83+
84+
def test_duplicate_implements_in_different_statement_fails(make_input_bundle):
85+
vyi = """
86+
@external
87+
def foo():
88+
...
89+
"""
90+
main = """
91+
import some_interface
92+
93+
implements: some_interface
94+
implements: some_interface
95+
96+
@external
97+
def foo(): # implementation
98+
pass
99+
"""
100+
input_bundle = make_input_bundle({"some_interface.vyi": vyi})
101+
102+
with pytest.raises(StructureException) as e:
103+
compile_code(main, input_bundle=input_bundle)
104+
105+
assert e.value._message == "some_interface implemented more than once"
106+
assert e.value._hint is None
107+
108+
109+
def test_duplicate_implements_in_different_statement_with_mixed_syntax_fails(make_input_bundle):
110+
some_interface = """
111+
@external
112+
def foo():
113+
...
114+
"""
115+
other_interface = """
116+
@external
117+
def bar():
118+
...
119+
"""
120+
main = """
121+
import some_interface
122+
import other_interface
123+
124+
implements: some_interface
125+
implements: (
126+
other_interface,
127+
some_interface,
128+
)
129+
130+
@external
131+
def foo(): # implementation
132+
pass
133+
134+
@external
135+
def bar(): # implementation
136+
pass
137+
"""
138+
input_bundle = make_input_bundle(
139+
{"some_interface.vyi": some_interface, "other_interface.vyi": other_interface}
140+
)
141+
142+
with pytest.raises(StructureException) as e:
143+
compile_code(main, input_bundle=input_bundle)
144+
145+
assert e.value._message == "some_interface implemented more than once"
146+
assert e.value._hint is None

tests/functional/syntax/test_interfaces.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,23 @@ def protected_view_fn() -> String[100]:
218218
""",
219219
InterfaceViolation,
220220
),
221+
(
222+
"""
223+
interface ITestInterface:
224+
def foo() -> uint256: view
225+
226+
interface ITestInterface2:
227+
def bar() -> uint256: view
228+
229+
implements: (
230+
ITestInterface,
231+
ITestInterface2,
232+
)
233+
234+
foo: public(constant(uint256)) = 1
235+
""",
236+
InterfaceViolation,
237+
),
221238
]
222239

223240

@@ -360,6 +377,45 @@ def foo() -> uint256: view
360377
def __init__(x: uint256):
361378
foo = x
362379
""",
380+
"""
381+
interface Foo:
382+
def foo() -> uint256: nonpayable
383+
384+
interface Bar:
385+
def bar() -> uint256: nonpayable
386+
387+
388+
implements: (
389+
Foo,
390+
Bar,
391+
)
392+
393+
@external
394+
def foo() -> uint256:
395+
return 0
396+
397+
@external
398+
def bar() -> uint256:
399+
return 0
400+
""",
401+
# single method can implement multiple interfaces
402+
"""
403+
interface Foo:
404+
def foo() -> uint256: nonpayable
405+
406+
interface Foo2:
407+
def foo() -> uint256: nonpayable
408+
409+
410+
implements: (
411+
Foo,
412+
Foo2,
413+
)
414+
415+
@external
416+
def foo() -> uint256:
417+
return 0
418+
""",
363419
# no namespace collision of interface after storage variable
364420
"""
365421
a: constant(uint256) = 1
@@ -502,6 +558,108 @@ def baz():
502558
assert compiler.compile_code(code, input_bundle=input_bundle) is not None
503559

504560

561+
def test_interfaces_in_different_files_check(make_input_bundle):
562+
foo = """
563+
def foo() -> uint256: ...
564+
"""
565+
bar = """
566+
def bar() -> uint256: ...
567+
"""
568+
569+
input_bundle = make_input_bundle({"foo.vyi": foo, "bar.vyi": bar})
570+
571+
code = """
572+
import foo as Foo
573+
import bar as Bar
574+
575+
implements: (
576+
Foo,
577+
Bar,
578+
)
579+
580+
@external
581+
def foo() -> uint256:
582+
return 0
583+
584+
@external
585+
def bar() -> uint256:
586+
return 0
587+
"""
588+
589+
res = compiler.compile_code(code, input_bundle=input_bundle)
590+
591+
assert res is not None
592+
593+
594+
def test_implements_in_interface_file_check(make_input_bundle):
595+
foo = """
596+
def foo() -> uint256: ...
597+
"""
598+
bar = """
599+
def bar() -> uint256: ...
600+
"""
601+
qux = """
602+
from interfaces import foo as Foo, bar as Bar
603+
604+
implements: (
605+
Foo,
606+
Bar,
607+
)
608+
609+
def foo() -> uint256: ...
610+
def bar() -> uint256: ...
611+
def qux() -> uint256: ...
612+
"""
613+
614+
input_bundle = make_input_bundle(
615+
{"interfaces/foo.vyi": foo, "interfaces/bar.vyi": bar, "interfaces/qux.vyi": qux}
616+
)
617+
618+
code = """
619+
from interfaces import qux as Qux
620+
621+
implements: (
622+
Qux,
623+
)
624+
625+
@external
626+
def foo() -> uint256:
627+
return 0
628+
629+
@external
630+
def bar() -> uint256:
631+
return 0
632+
633+
@external
634+
def qux() -> uint256:
635+
return 0
636+
"""
637+
638+
res = compiler.compile_code(code, input_bundle=input_bundle)
639+
640+
assert res is not None
641+
642+
643+
def test_interface_file_type_check(make_input_bundle):
644+
interface_code = """
645+
"""
646+
647+
input_bundle = make_input_bundle({"foo.vy": interface_code})
648+
649+
code = """
650+
import foo as Foo
651+
652+
implements: Foo
653+
"""
654+
with pytest.raises(StructureException) as e:
655+
compiler.compile_code(code, input_bundle=input_bundle)
656+
657+
assert (
658+
e.value._message == "Not an interface!"
659+
" (Since vyper v0.4.0, interface files are required to have a .vyi suffix.)"
660+
)
661+
662+
505663
invalid_visibility_code = [
506664
"""
507665
import foo as Foo

tests/unit/ast/test_ast_dict.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,18 @@ def foo() -> uint256:
9999
dict_out = compiler.compile_code(code, output_formats=["ast_dict"], source_id=0)
100100
assert dict_out["ast_dict"]["ast"]["body"][1] == {
101101
"col_offset": 0,
102-
"annotation": {
103-
"col_offset": 12,
104-
"end_col_offset": 15,
105-
"node_id": 12,
106-
"src": "60:3:0",
107-
"ast_type": "Name",
108-
"end_lineno": 5,
109-
"lineno": 5,
110-
"id": "Foo",
111-
},
102+
"children": [
103+
{
104+
"col_offset": 12,
105+
"end_col_offset": 15,
106+
"node_id": 12,
107+
"src": "60:3:0",
108+
"ast_type": "Name",
109+
"end_lineno": 5,
110+
"lineno": 5,
111+
"id": "Foo",
112+
}
113+
],
112114
"end_col_offset": 15,
113115
"node_id": 9,
114116
"src": "48:15:0",

0 commit comments

Comments
 (0)