From 6fc664a5a7af495d5d68ad50105683a04ae30330 Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Fri, 28 Aug 2026 12:05:16 +0200 Subject: [PATCH 1/2] feat[lang]: allow widening interfaces to addresses --- tests/functional/codegen/test_interfaces.py | 29 ++++++++++ .../syntax/modules/test_implements.py | 57 +++++++++++++++++++ tests/functional/syntax/test_interfaces.py | 10 ++++ vyper/semantics/types/module.py | 4 -- vyper/semantics/types/primitives.py | 6 ++ 5 files changed, 102 insertions(+), 4 deletions(-) diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index fbff29e85e..aacd5f4d7a 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -418,6 +418,35 @@ def test(addr: address): c.test(address) +def test_interface_widens_to_address_in_exprs(env, get_contract): + code = """ +interface Foo: + def foo(): payable + +f: Foo + +@internal +def _take_addr(a: address) -> address: + return a + +@external +def test(addr: address) -> address: + self.f = Foo(addr) + assert addr == self.f # widen from equals + + local_addr: address = self.f # widen from assignment + assert local_addr == addr + + passed: address = self._take_addr(self.f) # widen from argument passing + assert passed == addr + + return self.f # widen from return + """ + c = get_contract(code) + some_address = env.accounts[1] + assert c.test(some_address) == some_address + + # test data returned from external interface gets clamped @pytest.mark.parametrize("typ", ("int128", "uint8")) def test_external_interface_int_clampers(get_contract, tx_failed, typ): diff --git a/tests/functional/syntax/modules/test_implements.py b/tests/functional/syntax/modules/test_implements.py index d88fa43a37..36f28e034a 100644 --- a/tests/functional/syntax/modules/test_implements.py +++ b/tests/functional/syntax/modules/test_implements.py @@ -144,3 +144,60 @@ def bar(): # implementation assert e.value._message == "some_interface implemented more than once" assert e.value._hint is None + + +@pytest.mark.parametrize( + "var_decl,iface_method,store_stmt", + [ + pytest.param( + "foo: public(IERC20)", "def foo() -> address: view", "self.foo = asset_", id="storage" + ), + pytest.param( # GH issue 3954 + "foo: public(immutable(IERC20))", + "def foo() -> address: view", + "self.foo = asset_", + id="immutable", + ), + pytest.param( + "foo: public(HashMap[uint256, IERC20])", + "def foo(k: uint256) -> address: view", + "self.foo[7] = asset_", + id="hashmap", + ), + pytest.param( + "foo: public(DynArray[IERC20, 3])", + "def foo(i: uint256) -> address: view", + "self.foo.append(asset_)", + id="dynarray", + ), + pytest.param( # GH issue 4721 + "foo: public(IERC20)", + "def foo() -> IERC20: view", + "self.foo = asset_", + id="interface_return", + ), + ], +) +def test_implements_with_public_interface(env, var_decl, iface_method, store_stmt): + """ + Tests that `var_decl` correctly implements `iface_method` + + For example `foo: public(IERC20)` implements `def foo() -> address: view` + """ + + main = f""" +from ethereum.ercs import IERC20 + +{var_decl} + +interface IAsset: + {iface_method} + +implements: IAsset + +@deploy +def __init__(asset_: IERC20): + {store_stmt} + """ + some_address = env.accounts[1] + compile_code(main, some_address) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index dfb25d5983..be0d9b88d2 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -266,6 +266,16 @@ def f(): """, InterfaceViolation, ), + ( + """ +from ethereum.ercs import IERC20 + +@external +def test(a: address): + x: IERC20 = a # Should not narrow + """, + TypeMismatch, + ), ] diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 3daa4e8226..924a9db1de 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -77,10 +77,6 @@ def get_type_member(self, attr, node): # get an event, struct or flag from this interface return TYPE_T(self._helper.get_member(attr, node)) - @property - def getter_signature(self): - return (), AddressT() - @property def abi_type(self) -> ABIType: return ABI_Address() diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 9849e125a3..7a0a9587c3 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -439,6 +439,12 @@ def validate_literal(self, node: vy_ast.Constant) -> None: node, ) + def compare_type(self, other): + from vyper.semantics.types.module import InterfaceT + + # interfaces can be widened to addresses + return isinstance(other, InterfaceT) or super().compare_type(other) + # type for "self" # refactoring note: it might be best for this to be a ModuleT actually From 614f81e5bed803047d7c004440186b37dacaf630 Mon Sep 17 00:00:00 2001 From: Quentin Bernet Date: Fri, 28 Aug 2026 16:33:18 +0200 Subject: [PATCH 2/2] fix test straggler --- tests/functional/syntax/modules/test_implements.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/functional/syntax/modules/test_implements.py b/tests/functional/syntax/modules/test_implements.py index 36f28e034a..214d79f8f9 100644 --- a/tests/functional/syntax/modules/test_implements.py +++ b/tests/functional/syntax/modules/test_implements.py @@ -178,7 +178,7 @@ def bar(): # implementation ), ], ) -def test_implements_with_public_interface(env, var_decl, iface_method, store_stmt): +def test_implements_with_public_interface(var_decl, iface_method, store_stmt): """ Tests that `var_decl` correctly implements `iface_method` @@ -199,5 +199,4 @@ def test_implements_with_public_interface(env, var_decl, iface_method, store_stm def __init__(asset_: IERC20): {store_stmt} """ - some_address = env.accounts[1] - compile_code(main, some_address) + compile_code(main)