From 3daf88bc348f454bfbcfbaf7da9edc655f1de48b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 08:43:08 -0400 Subject: [PATCH 01/28] feat[lang]: allow module intrinsic interface call allow `module.__interface__` to be used in call position by adding it to the module membership data structure. additionally, fix a bug where interfaces defined inline could not be exported. this is simultaneously fixed as a related bug because previously, interfaces could come up in export analysis as `InterfaceT` or `TYPE_T` depending on their provenance. this commit fixes the bug by making them `TYPE_T` in both imported and inlined provenance. refactor: - wrap interfaces in TYPE_T - streamline an `isinstance(t, (VyperType, TYPE_T))` check. `TYPE_T` now inherits from `VyperType`, so it doesn't need to be listed separately --- vyper/semantics/analysis/module.py | 10 ++++++---- vyper/semantics/analysis/utils.py | 2 +- vyper/semantics/types/module.py | 13 ++++++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index d0b019db7a..dde3c0b4b3 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -54,7 +54,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.namespace import Namespace, get_namespace, override_global_namespace -from vyper.semantics.types import EventT, FlagT, InterfaceT, StructT +from vyper.semantics.types import EventT, FlagT, InterfaceT, StructT, is_type_t from vyper.semantics.types.function import ContractFunctionT from vyper.semantics.types.module import ModuleT from vyper.semantics.types.utils import type_from_annotation @@ -547,7 +547,9 @@ def visit_ExportsDecl(self, node): elif isinstance(info.typ, ContractFunctionT): # regular function funcs = [info.typ] - elif isinstance(info.typ, InterfaceT): + elif is_type_t(info.typ, InterfaceT): + interface_t = info.typ.typedef + if not isinstance(item, vy_ast.Attribute): raise StructureException( "invalid export", @@ -558,7 +560,7 @@ def visit_ExportsDecl(self, node): if module_info is None: raise StructureException("not a valid module!", item.value) - if info.typ not in module_info.typ.implemented_interfaces: + if interface_t not in module_info.typ.implemented_interfaces: iface_str = item.node_source_code module_str = item.value.node_source_code msg = f"requested `{iface_str}` but `{module_str}`" @@ -569,7 +571,7 @@ def visit_ExportsDecl(self, node): # find the specific implementation of the function in the module funcs = [ module_exposed_fns[fn.name] - for fn in info.typ.functions.values() + for fn in interface_t.functions.values() if fn.is_external ] else: diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index be323b1d13..d0e3a70d0f 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -197,7 +197,7 @@ def _raise_invalid_reference(name, node): try: s = t.get_member(name, node) - if isinstance(s, (VyperType, TYPE_T)): + if isinstance(s, VyperType): # ex. foo.bar(). bar() is a ContractFunctionT return [s] diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index b3e3f2ef2b..11c05b7ad9 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -322,16 +322,23 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): for i in self.import_stmts: import_info = i._metadata["import_info"] - self.add_member(import_info.alias, import_info.typ) if hasattr(import_info.typ, "module_t"): - self._helper.add_member(import_info.alias, TYPE_T(import_info.typ)) + module_info = import_info.typ + # get_expr_info uses ModuleInfo + self.add_member(import_info.alias, module_info) + # type_from_annotation uses TYPE_T + self._helper.add_member(import_info.alias, TYPE_T(module_info.module_t)) + else: # interfaces + self.add_member(import_info.alias, TYPE_T(import_info.typ)) for name, interface_t in self.interfaces.items(): # can access interfaces in type position self._helper.add_member(name, TYPE_T(interface_t)) - self.add_member("__interface__", self.interface) + # can use module.__interface__ in call position + self.add_member("__interface__", TYPE_T(self.interface)) + self._helper.add_member("__interface__", TYPE_T(self.interface)) # __eq__ is very strict on ModuleT - object equality! this is because we # don't want to reason about where a module came from (i.e. input bundle, From dad60ac189b3a6c542e5b373d88295de44a90099 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 09:35:28 -0400 Subject: [PATCH 02/28] add codegen test --- .../codegen/modules/test_interface_imports.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/modules/test_interface_imports.py b/tests/functional/codegen/modules/test_interface_imports.py index c0fae6496c..0215c2c5b4 100644 --- a/tests/functional/codegen/modules/test_interface_imports.py +++ b/tests/functional/codegen/modules/test_interface_imports.py @@ -50,11 +50,38 @@ def foo() -> bool: # check that this typechecks both directions a: lib1.IERC20 = IERC20(msg.sender) b: lib2.IERC20 = IERC20(msg.sender) + c: IERC20 = lib1.IERC20(msg.sender) # allowed in call position # return the equality so we can sanity check it - return a == b + return a == b and b == c """ input_bundle = make_input_bundle({"lib1.vy": lib1, "lib2.vy": lib2}) c = get_contract(main, input_bundle=input_bundle) assert c.foo() is True + +def test_intrinsic_interface(get_contract, make_input_bundle): + lib = """ +@external +@view +def foo() -> uint256: + if msg.sender == self: + return 4 + else: + return 5 + """ + main = """ +import lib + +exports: lib.__interface__ + +@external +@view +def bar() -> uint256: + return staticcall lib.__interface__(self).foo() + """ + input_bundle = make_input_bundle({"lib.vy": lib}) + c = get_contract(main, input_bundle=input_bundle) + + assert c.foo() == 5 + assert c.bar() == 4 From 08c652a92d1681fc457ff6d8e9c8784a95efb286 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 09:42:01 -0400 Subject: [PATCH 03/28] add export test for unimplemented _inline_ interfaces there was a test for unimplemented `.vyi` interfaces, this commit adds a test for unimplemented inline interface for completeness --- .../functional/syntax/modules/test_exports.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 7b00d29c98..e3db625f87 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -384,6 +384,27 @@ def do_xyz(): compile_code(main, input_bundle=input_bundle) assert e.value._message == "requested `lib1.ifoo` but `lib1` does not implement `lib1.ifoo`!" +def test_no_export_unimplemented_inline_interface(make_input_bundle): + lib1 = """ +interface ifoo: + def do_xyz(): nonpayable + +# technically implements ifoo, but missing `implements: ifoo` + +@external +def do_xyz(): + pass + """ + main = """ +import lib1 + +exports: lib1.ifoo + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + with pytest.raises(InterfaceViolation) as e: + compile_code(main, input_bundle=input_bundle) + assert e.value._message == "requested `lib1.ifoo` but `lib1` does not implement `lib1.ifoo`!" + def test_export_selector_conflict(make_input_bundle): ifoo = """ From fe1cfa4cc6ec3830d78a29ea2072d7e19a3c61e9 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 09:45:33 -0400 Subject: [PATCH 04/28] add codegen test for inline interface export --- .../codegen/modules/test_exports.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/functional/codegen/modules/test_exports.py b/tests/functional/codegen/modules/test_exports.py index 93f4fe6c2f..3cc21d61a9 100644 --- a/tests/functional/codegen/modules/test_exports.py +++ b/tests/functional/codegen/modules/test_exports.py @@ -440,3 +440,26 @@ def __init__(): # call `c.__default__()` env.message_call(c.address) assert c.counter() == 6 + + +def test_inline_interface_export(make_input_bundle, get_contract): + lib1 = """ +interface IAsset: + def asset() -> address: view + +implements: IAsset + +@external +@view +def asset() -> address: + return self + """ + main = """ +import lib1 + +exports: lib1.IAsset + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + c = get_contract(main, input_bundle=input_bundle) + + assert c.asset() == c.address From 15d0fde53c4e6cd59f4c95afc4fe85fd739663cc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 09:45:44 -0400 Subject: [PATCH 05/28] fix lint --- tests/functional/codegen/modules/test_interface_imports.py | 2 ++ tests/functional/syntax/modules/test_exports.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/functional/codegen/modules/test_interface_imports.py b/tests/functional/codegen/modules/test_interface_imports.py index 0215c2c5b4..74066cb876 100644 --- a/tests/functional/codegen/modules/test_interface_imports.py +++ b/tests/functional/codegen/modules/test_interface_imports.py @@ -60,11 +60,13 @@ def foo() -> bool: assert c.foo() is True + def test_intrinsic_interface(get_contract, make_input_bundle): lib = """ @external @view def foo() -> uint256: + # detect self call if msg.sender == self: return 4 else: diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index e3db625f87..441c975317 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -384,6 +384,7 @@ def do_xyz(): compile_code(main, input_bundle=input_bundle) assert e.value._message == "requested `lib1.ifoo` but `lib1` does not implement `lib1.ifoo`!" + def test_no_export_unimplemented_inline_interface(make_input_bundle): lib1 = """ interface ifoo: From 2d5f67df0e18e506918965a84722363b169c913e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 8 Aug 2024 22:07:45 +0800 Subject: [PATCH 06/28] add sanity check --- vyper/semantics/types/module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 86240aa509..3ffca571bd 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -331,6 +331,7 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # type_from_annotation uses TYPE_T self._helper.add_member(import_info.alias, TYPE_T(module_info.module_t)) else: # interfaces + assert isinstance(import_info, InterfaceT) self.add_member(import_info.alias, TYPE_T(import_info.typ)) for name, interface_t in self.interfaces.items(): From 4986b50a3ffd93400de6a5d80c0989f2537535f5 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 19 Oct 2024 15:23:26 -0400 Subject: [PATCH 07/28] fix lint --- tests/functional/codegen/modules/test_interface_imports.py | 1 + vyper/semantics/types/module.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/modules/test_interface_imports.py b/tests/functional/codegen/modules/test_interface_imports.py index 773e21c5b8..253335f1bb 100644 --- a/tests/functional/codegen/modules/test_interface_imports.py +++ b/tests/functional/codegen/modules/test_interface_imports.py @@ -88,6 +88,7 @@ def bar() -> uint256: assert c.foo() == 5 assert c.bar() == 4 + def test_import_interface_flags(make_input_bundle, get_contract): ifaces = """ flag Foo: diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 28b13ed136..66eb1ef390 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -338,7 +338,7 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # type_from_annotation uses TYPE_T self._helper.add_member(import_info.alias, TYPE_T(module_info.module_t)) else: # interfaces - assert isinstance(import_info, InterfaceT) + assert isinstance(import_info.typ, InterfaceT) self.add_member(import_info.alias, TYPE_T(import_info.typ)) for name, interface_t in self.interfaces.items(): From 24ac428539955f272a08e49301fbd8107d30b048 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 19 Oct 2024 15:47:37 -0400 Subject: [PATCH 08/28] reject empty interfaces --- tests/functional/syntax/modules/test_exports.py | 16 ++++++++++++++++ vyper/semantics/analysis/base.py | 1 + vyper/semantics/analysis/module.py | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 441c975317..9961e44e5b 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -466,3 +466,19 @@ def __init__(): with pytest.raises(InterfaceViolation) as e: compile_code(main, input_bundle=input_bundle) assert e.value._message == "requested `lib1.ifoo` but `lib1` does not implement `lib1.ifoo`!" + + +def test_export_empty_interface(make_input_bundle): + lib1 = """ +def an_internal_function(): + pass + """ + main = """ +import lib1 + +exports: lib1.__interface__ + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + with pytest.raises(StructureException) as e: + compile_code(main, input_bundle=input_bundle) + assert e.value._message == "lib1 has no external functions!" diff --git a/vyper/semantics/analysis/base.py b/vyper/semantics/analysis/base.py index e275930fa0..adfc7540a0 100644 --- a/vyper/semantics/analysis/base.py +++ b/vyper/semantics/analysis/base.py @@ -96,6 +96,7 @@ class AnalysisResult: class ModuleInfo(AnalysisResult): module_t: "ModuleT" alias: str + # import_node: vy_ast._ImportStmt # maybe could be useful ownership: ModuleOwnership = ModuleOwnership.NO_OWNERSHIP ownership_decl: Optional[vy_ast.VyperNode] = None diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index acbf02b719..34e1624655 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -528,6 +528,12 @@ def visit_ExportsDecl(self, node): for fn in interface_t.functions.values() if fn.is_external ] + + if len(funcs) == 0: + path = module_info.module_node.path + msg = f"{module_info.alias} (located at `{path}`) has no external functions!" + raise StructureException(msg, item) + else: raise StructureException( f"not a function or interface: `{info.typ}`", info.typ.decl_node, item From c0f37acab7bdd5b78d907f614dfecbec0324bbd4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 19 Oct 2024 16:08:09 -0400 Subject: [PATCH 09/28] fix a test --- tests/functional/syntax/modules/test_exports.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 9961e44e5b..5c4a8067e1 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -481,4 +481,5 @@ def an_internal_function(): input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: compile_code(main, input_bundle=input_bundle) - assert e.value._message == "lib1 has no external functions!" + lib1_path = input_bundle.load_file("lib1.vy").path + assert e.value._message == f"lib1 (located at `{lib1_path}`) has no external functions!" From 59e298e6e668f04b235f6b7707470a0fb479c775 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 19 Oct 2024 16:16:42 -0400 Subject: [PATCH 10/28] fix again --- tests/functional/syntax/modules/test_exports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 5c4a8067e1..4f9f0268cc 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -468,7 +468,7 @@ def __init__(): assert e.value._message == "requested `lib1.ifoo` but `lib1` does not implement `lib1.ifoo`!" -def test_export_empty_interface(make_input_bundle): +def test_export_empty_interface(make_input_bundle, tmp_path): lib1 = """ def an_internal_function(): pass @@ -481,5 +481,5 @@ def an_internal_function(): input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: compile_code(main, input_bundle=input_bundle) - lib1_path = input_bundle.load_file("lib1.vy").path + lib1_path = tmp_path / "lib1.vy" assert e.value._message == f"lib1 (located at `{lib1_path}`) has no external functions!" From e05fabfc06307cd099a7db0130af0df1417d7e81 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 20 Oct 2024 11:05:17 -0400 Subject: [PATCH 11/28] remove init function from interfaces it was only there for `-f abi` output -- since it is a standards requirement to have the constructor in the abi output, but it doesn't semantically make sense in-language for the init function to be the interface, we add it back in later, at abi generation time. add a test that `module.__interface__(...).__init__()` is not allowed. --- .../syntax/modules/test_deploy_visibility.py | 32 ++++++++++++++++++- vyper/compiler/output.py | 3 ++ vyper/semantics/types/base.py | 10 +++--- vyper/semantics/types/module.py | 3 -- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tests/functional/syntax/modules/test_deploy_visibility.py b/tests/functional/syntax/modules/test_deploy_visibility.py index f51bf9575b..740c3a6a55 100644 --- a/tests/functional/syntax/modules/test_deploy_visibility.py +++ b/tests/functional/syntax/modules/test_deploy_visibility.py @@ -1,7 +1,7 @@ import pytest from vyper.compiler import compile_code -from vyper.exceptions import CallViolation +from vyper.exceptions import CallViolation, UnknownAttribute def test_call_deploy_from_external(make_input_bundle): @@ -25,3 +25,33 @@ def foo(): compile_code(main, input_bundle=input_bundle) assert e.value.message == "Cannot call an @deploy function from an @external function!" + + +def test_module_interface_init(make_input_bundle, tmp_path): + lib1 = """ +#lib1.vy +k: uint256 + +@external +def bar(): + pass + +@deploy +def __init__(): + self.k = 10 + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + + code = """ +import lib1 + +@deploy +def __init__(): + lib1.__interface__(self).__init__() + """ + + with pytest.raises(UnknownAttribute) as e: + compile_code(code, input_bundle=input_bundle) + + lib1_path = tmp_path / "lib1.vy" + assert e.value.message == f"interface {lib1_path} has no member '__init__'." diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index f5f99a0bc3..e0eea293bc 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -268,6 +268,9 @@ def build_abi_output(compiler_data: CompilerData) -> list: _ = compiler_data.ir_runtime # ensure _ir_info is generated abi = module_t.interface.to_toplevel_abi_dict() + if module_t.init_function: + abi += module_t.init_function.to_toplevel_abi_dict() + if compiler_data.show_gas_estimates: # Add gas estimates for each function to ABI gas_estimates = build_gas_estimates(compiler_data.function_signatures) diff --git a/vyper/semantics/types/base.py b/vyper/semantics/types/base.py index 128ede0d5b..aca37b33a3 100644 --- a/vyper/semantics/types/base.py +++ b/vyper/semantics/types/base.py @@ -114,8 +114,13 @@ def __eq__(self, other): ) def __lt__(self, other): + # CMC 2024-10-20 what is this for? return self.abi_type.selector_name() < other.abi_type.selector_name() + def __repr__(self): + # TODO: add `pretty()` to the VyperType API? + return self._id + # return a dict suitable for serializing in the AST def to_dict(self): ret = {"name": self._id} @@ -362,10 +367,7 @@ def get_member(self, key: str, node: vy_ast.VyperNode) -> "VyperType": raise StructureException(f"{self} instance does not have members", node) hint = get_levenshtein_error_suggestions(key, self.members, 0.3) - raise UnknownAttribute(f"{self} has no member '{key}'.", node, hint=hint) - - def __repr__(self): - return self._id + raise UnknownAttribute(f"{repr(self)} has no member '{key}'.", node, hint=hint) class KwargSettings: diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 66eb1ef390..bff8b60d82 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -240,9 +240,6 @@ def from_ModuleT(cls, module_t: "ModuleT") -> "InterfaceT": for fn_t in module_t.exposed_functions: funcs.append((fn_t.name, fn_t)) - if (fn_t := module_t.init_function) is not None: - funcs.append((fn_t.name, fn_t)) - event_set: OrderedSet[EventT] = OrderedSet() event_set.update([node._metadata["event_type"] for node in module_t.event_defs]) event_set.update(module_t.used_events) From 7a2d36d9d8fc15de65deff61790a64310acf8edc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 20 Oct 2024 11:07:28 -0400 Subject: [PATCH 12/28] fix for windows tests --- tests/functional/syntax/modules/test_deploy_visibility.py | 3 ++- tests/functional/syntax/modules/test_exports.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/modules/test_deploy_visibility.py b/tests/functional/syntax/modules/test_deploy_visibility.py index 740c3a6a55..428103f8cd 100644 --- a/tests/functional/syntax/modules/test_deploy_visibility.py +++ b/tests/functional/syntax/modules/test_deploy_visibility.py @@ -53,5 +53,6 @@ def __init__(): with pytest.raises(UnknownAttribute) as e: compile_code(code, input_bundle=input_bundle) - lib1_path = tmp_path / "lib1.vy" + # as_posix() for windows tests + lib1_path = (tmp_path / "lib1.vy").as_posix() assert e.value.message == f"interface {lib1_path} has no member '__init__'." diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 4f9f0268cc..def1849d58 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -481,5 +481,7 @@ def an_internal_function(): input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: compile_code(main, input_bundle=input_bundle) - lib1_path = tmp_path / "lib1.vy" + + # as_posix() for windows + lib1_path = (tmp_path / "lib1.vy").as_posix() assert e.value._message == f"lib1 (located at `{lib1_path}`) has no external functions!" From ac43bebccc5a88e7a9b31e9ca5c5cbdc46f0fc5a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 22 Oct 2024 09:29:03 -0400 Subject: [PATCH 13/28] reject weird exports of value types also add a test for an exception case that was not tested before --- .../functional/syntax/modules/test_exports.py | 33 +++++++++++++++++++ vyper/semantics/analysis/module.py | 10 +++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index def1849d58..aa92118d20 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -485,3 +485,36 @@ def an_internal_function(): # as_posix() for windows lib1_path = (tmp_path / "lib1.vy").as_posix() assert e.value._message == f"lib1 (located at `{lib1_path}`) has no external functions!" + + +def test_invalid_export(make_input_bundle): + lib1 = """ +@external +def foo(): + pass + """ + main = """ +import lib1 +a: address + +exports: lib1.__interface__(self.a).foo + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + + with pytest.raises(StructureException) as e: + compile_code(main, input_bundle=input_bundle) + + assert e.value._message == "invalid export of a value" + assert e.value._hint == "exports should look like ." + + main = """ +interface Foo: + def foo(): nonpayable + +exports: Foo + """ + with pytest.raises(StructureException) as e: + compile_code(main) + + assert e.value._message == "invalid export" + assert e.value._hint == "exports should look like ." diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 34e1624655..737f675b7c 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -40,7 +40,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.namespace import Namespace, get_namespace, override_global_namespace -from vyper.semantics.types import EventT, FlagT, InterfaceT, StructT, is_type_t +from vyper.semantics.types import TYPE_T, EventT, FlagT, InterfaceT, StructT, is_type_t from vyper.semantics.types.function import ContractFunctionT from vyper.semantics.types.module import ModuleT from vyper.semantics.types.utils import type_from_annotation @@ -499,6 +499,14 @@ def visit_ExportsDecl(self, node): raise StructureException("not a public variable!", decl, item) funcs = [decl._expanded_getter._metadata["func_type"]] elif isinstance(info.typ, ContractFunctionT): + # e.g. lib1.__interface__(self._addr).foo + if not isinstance(get_expr_info(item.value).typ, (ModuleT, TYPE_T)): + raise StructureException( + "invalid export of a value", + item.value, + hint="exports should look like .", + ) + # regular function funcs = [info.typ] elif is_type_t(info.typ, InterfaceT): From df217e8170bb8d6f13cb8364320954f8f3b13869 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 10 Nov 2024 11:23:45 +0100 Subject: [PATCH 14/28] add `module.__at__` --- vyper/codegen/expr.py | 24 +++++++++++++++++------- vyper/semantics/types/__init__.py | 4 ++-- vyper/semantics/types/module.py | 20 +++++++++++++++++--- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index cd51966710..3a09bbe6c0 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -51,6 +51,7 @@ FlagT, HashMapT, InterfaceT, + ModuleT, SArrayT, StringT, StructT, @@ -680,7 +681,8 @@ def parse_Call(self): # TODO fix cyclic import from vyper.builtins._signatures import BuiltinFunctionT - func_t = self.expr.func._metadata["type"] + func = self.expr.func + func_t = func._metadata["type"] if isinstance(func_t, BuiltinFunctionT): return func_t.build_IR(self.expr, self.context) @@ -691,8 +693,14 @@ def parse_Call(self): return self.handle_struct_literal() # Interface constructor. Bar(
). - if is_type_t(func_t, InterfaceT): + if is_type_t(func_t, InterfaceT) or func.get("attr") == "__at__": assert not self.is_stmt # sanity check typechecker + + # magic: do sanity checks for module.__at__ + if func.get("attr") == "__at__": + assert isinstance(func_t, MemberFunctionT) + assert isinstance(func.value._metadata["type"], ModuleT) + (arg0,) = self.expr.args arg_ir = Expr(arg0, self.context).ir_node @@ -702,16 +710,16 @@ def parse_Call(self): return arg_ir if isinstance(func_t, MemberFunctionT): - darray = Expr(self.expr.func.value, self.context).ir_node + # TODO consider moving these to builtins or a dedicated file + darray = Expr(func.value, self.context).ir_node assert isinstance(darray.typ, DArrayT) args = [Expr(x, self.context).ir_node for x in self.expr.args] - if self.expr.func.attr == "pop": - # TODO consider moving this to builtins - darray = Expr(self.expr.func.value, self.context).ir_node + if func.attr == "pop": + darray = Expr(func.value, self.context).ir_node assert len(self.expr.args) == 0 return_item = not self.is_stmt return pop_dyn_array(darray, return_popped_item=return_item) - elif self.expr.func.attr == "append": + elif func.attr == "append": (arg,) = args check_assign( dummy_node_for_type(darray.typ.value_type), dummy_node_for_type(arg.typ) @@ -726,6 +734,8 @@ def parse_Call(self): ret.append(append_dyn_array(darray, arg)) return IRnode.from_list(ret) + raise CompilerPanic("unreachable!") # pragma: nocover + assert isinstance(func_t, ContractFunctionT) assert func_t.is_internal or func_t.is_constructor diff --git a/vyper/semantics/types/__init__.py b/vyper/semantics/types/__init__.py index 59a20dd99f..b881f52b2b 100644 --- a/vyper/semantics/types/__init__.py +++ b/vyper/semantics/types/__init__.py @@ -1,8 +1,8 @@ from . import primitives, subscriptable, user from .base import TYPE_T, VOID_TYPE, KwargSettings, VyperType, is_type_t, map_void from .bytestrings import BytesT, StringT, _BytestringT -from .function import MemberFunctionT -from .module import InterfaceT +from .function import ContractFunctionT, MemberFunctionT +from .module import InterfaceT, ModuleT from .primitives import AddressT, BoolT, BytesM_T, DecimalT, IntegerT, SelfT from .subscriptable import DArrayT, HashMapT, SArrayT, TupleT from .user import EventT, FlagT, StructT diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index bff8b60d82..bfbddb8b37 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -19,7 +19,7 @@ ) from vyper.semantics.data_locations import DataLocation from vyper.semantics.types.base import TYPE_T, VyperType, is_type_t -from vyper.semantics.types.function import ContractFunctionT +from vyper.semantics.types.function import ContractFunctionT, MemberFunctionT from vyper.semantics.types.primitives import AddressT from vyper.semantics.types.user import EventT, FlagT, StructT, _UserType from vyper.utils import OrderedSet @@ -270,6 +270,19 @@ def from_InterfaceDef(cls, node: vy_ast.InterfaceDef) -> "InterfaceT": return cls._from_lists(node.name, node, functions) +def _module_at(module_t): + return MemberFunctionT( + # set underlying_type to a TYPE_T as a bit of a kludge, since it's + # kind of like a class method (but we don't have classmethod + # abstraction) + underlying_type=TYPE_T(module_t), + name="__at__", + arg_types=[AddressT()], + return_type=module_t.interface, + is_modifying=False, + ) + + # Datatype to store all module information. class ModuleT(VyperType): typeclass = "module" @@ -342,8 +355,9 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # can access interfaces in type position self._helper.add_member(name, TYPE_T(interface_t)) - # can use module.__interface__ in call position - self.add_member("__interface__", TYPE_T(self.interface)) + # module.__at__(addr) + self.add_member("__at__", _module_at(self)) + # `module.__interface__` (in type position) self._helper.add_member("__interface__", TYPE_T(self.interface)) # __eq__ is very strict on ModuleT - object equality! this is because we From dfb3eb3db214c141a02f890bb042d8c7f4de099b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 10 Nov 2024 11:38:45 +0100 Subject: [PATCH 15/28] fix exports --- vyper/semantics/types/function.py | 2 +- vyper/semantics/types/module.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 7a56b01281..ffeb5b7299 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -874,7 +874,7 @@ def _id(self): return self.name def __repr__(self): - return f"{self.underlying_type._id} member function '{self.name}'" + return f"{self.underlying_type} member function '{self.name}'" def fetch_call_return(self, node: vy_ast.Call) -> Optional[VyperType]: validate_call_args(node, len(self.arg_types)) diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index bfbddb8b37..498757b94e 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -357,7 +357,10 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # module.__at__(addr) self.add_member("__at__", _module_at(self)) - # `module.__interface__` (in type position) + + # allow `module.__interface__` (in exports declarations) + self.add_member("__interface__", TYPE_T(self.interface)) + # allow `module.__interface__` (in type position) self._helper.add_member("__interface__", TYPE_T(self.interface)) # __eq__ is very strict on ModuleT - object equality! this is because we From 236e11a3435c5775fd6b4a6e413124c5db82dfa0 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:02:39 +0000 Subject: [PATCH 16/28] add invalid at exports --- .../functional/syntax/modules/test_exports.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index aa92118d20..4314c1bbf0 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -518,3 +518,35 @@ def foo(): nonpayable assert e.value._message == "invalid export" assert e.value._hint == "exports should look like ." + + +@pytest.mark.parametrize("exports_item", ["__at__", "__at__(self)", "__at__(self).__interface__"]) +def test_invalid_at_exports(get_contract, make_input_bundle, exports_item): + lib = """ +@external +@view +def foo() -> uint256: + return 5 + """ + + main = f""" +import lib + +exports: lib.{exports_item} + +@external +@view +def bar() -> uint256: + return staticcall lib.__at__(self).foo() + """ + input_bundle = make_input_bundle({"lib.vy": lib}) + + with pytest.raises(Exception) as e: + compile_code(main, input_bundle=input_bundle) + + if exports_item == "__at__": + assert "not a function or interface" in str(e.value) + if exports_item == "__at__(self)": + assert "invalid exports" in str(e.value) + if exports_item == "__at__(self).__interface__": + assert "has no member '__interface__'" in str(e.value) From 2b5c4addc9819c0d5974158306b9c67cca528433 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:04:00 +0000 Subject: [PATCH 17/28] parametrize over interface accessor --- .../functional/codegen/modules/test_interface_imports.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/functional/codegen/modules/test_interface_imports.py b/tests/functional/codegen/modules/test_interface_imports.py index 253335f1bb..2f6d548987 100644 --- a/tests/functional/codegen/modules/test_interface_imports.py +++ b/tests/functional/codegen/modules/test_interface_imports.py @@ -61,7 +61,8 @@ def foo() -> bool: assert c.foo() is True -def test_intrinsic_interface(get_contract, make_input_bundle): +@pytest.mark.parametrize("interface_syntax", ["__at__", "__interface__"]) +def test_intrinsic_interface(get_contract, make_input_bundle, interface_syntax): lib = """ @external @view @@ -72,7 +73,8 @@ def foo() -> uint256: else: return 5 """ - main = """ + + main = f""" import lib exports: lib.__interface__ @@ -80,7 +82,7 @@ def foo() -> uint256: @external @view def bar() -> uint256: - return staticcall lib.__interface__(self).foo() + return staticcall lib.{interface_syntax}(self).foo() """ input_bundle = make_input_bundle({"lib.vy": lib}) c = get_contract(main, input_bundle=input_bundle) From 3db8f42553626adde024d8af70ebbfe3218b2804 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:24:21 +0000 Subject: [PATCH 18/28] add intrinsic interface instantiation test --- tests/functional/codegen/test_interfaces.py | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index 8887bf07cb..aec21bdc81 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -774,3 +774,26 @@ def foo(s: MyStruct) -> MyStruct: assert "b: uint256" in out assert "struct Voter:" in out assert "voted: bool" in out + + +def test_intrinsic_interface_instantiation(make_input_bundle, get_contract): + lib1 = """ +@external +@view +def foo(): + pass + """ + main = """ +import lib1 + +i: lib1.__interface__ + +@external +def bar() -> lib1.__interface__: + self.i = lib1.__at__(self) + return self.i + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + c = get_contract(main, input_bundle=input_bundle) + + assert c.bar() == c.address From 5b9145881668912b07a5e37e3d4f329232805840 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:26:21 +0000 Subject: [PATCH 19/28] add intrinsic interface convert test --- tests/functional/codegen/test_interfaces.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index aec21bdc81..229bebfc1f 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -797,3 +797,23 @@ def bar() -> lib1.__interface__: c = get_contract(main, input_bundle=input_bundle) assert c.bar() == c.address + + +def test_intrinsic_interface_converts(make_input_bundle, get_contract): + lib1 = """ +@external +@view +def foo(): + pass + """ + main = """ +import lib1 + +@external +def bar() -> lib1.__interface__: + return lib1.__at__(self) + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + c = get_contract(main, input_bundle=input_bundle) + + assert c.bar() == c.address From 7ebf244fee96e61ae0cbbb3f543ebad19999a5ca Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:33:31 +0000 Subject: [PATCH 20/28] add intrinsic interfaces have different types --- tests/functional/syntax/test_interfaces.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index 86ea4bcfd0..204b03ceb7 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -571,3 +571,30 @@ def bar(): compiler.compile_code(code, input_bundle=input_bundle) assert e.value.message == "Contract does not implement all interface functions: bar(), foobar()" + + +def test_intrinsic_interfaces_different_types(make_input_bundle, get_contract): + lib1 = """ +@external +@view +def foo(): + pass + """ + lib2 = """ +@external +@view +def foo(): + pass + """ + main = """ +import lib1 +import lib2 + +@external +def bar(): + assert lib1.__at__(self) == lib2.__at__(self) + """ + input_bundle = make_input_bundle({"lib1.vy": lib1, "lib2.vy": lib2}) + + with pytest.raises(TypeMismatch): + compiler.compile_code(main, input_bundle=input_bundle) From 272d4d55190c29b88212f88f1eee69c6b59e4db2 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:39:20 +0000 Subject: [PATCH 21/28] extend interface init function test --- tests/functional/syntax/modules/test_deploy_visibility.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/syntax/modules/test_deploy_visibility.py b/tests/functional/syntax/modules/test_deploy_visibility.py index 428103f8cd..4eb107708d 100644 --- a/tests/functional/syntax/modules/test_deploy_visibility.py +++ b/tests/functional/syntax/modules/test_deploy_visibility.py @@ -26,8 +26,8 @@ def foo(): assert e.value.message == "Cannot call an @deploy function from an @external function!" - -def test_module_interface_init(make_input_bundle, tmp_path): +@pytest.mark.parametrize("interface_syntax", ["__interface__", "__at__"]) +def test_module_interface_init(make_input_bundle, tmp_path, interface_syntax): lib1 = """ #lib1.vy k: uint256 @@ -42,12 +42,12 @@ def __init__(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) - code = """ + code = f""" import lib1 @deploy def __init__(): - lib1.__interface__(self).__init__() + lib1.{interface_syntax}(self).__init__() """ with pytest.raises(UnknownAttribute) as e: From 68608971f4da348828e446ba1cb4ee50f3f51fe9 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 12:48:18 +0000 Subject: [PATCH 22/28] add test accesing default function in interface --- tests/functional/syntax/test_interfaces.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index 204b03ceb7..7a177ad3d4 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -598,3 +598,24 @@ def bar(): with pytest.raises(TypeMismatch): compiler.compile_code(main, input_bundle=input_bundle) + + +def test_intrinsic_interfaces_default_function(make_input_bundle, get_contract): + lib1 = """ +@external +@payable +def __default__(): + pass + """ + main = """ +import lib1 + +@external +def bar(): + extcall lib1.__at__(self).__default__() + + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + + with pytest.raises(Exception): + compiler.compile_code(main, input_bundle=input_bundle) From 73aeacc0608197dd55ef9c3e719e41b0f9e4f45e Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 13:32:59 +0000 Subject: [PATCH 23/28] add more interface tests --- tests/functional/codegen/test_interfaces.py | 48 ++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index 229bebfc1f..9e0c2ce0c3 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -788,7 +788,7 @@ def foo(): i: lib1.__interface__ -@external +@external def bar() -> lib1.__interface__: self.i = lib1.__at__(self) return self.i @@ -817,3 +817,49 @@ def bar() -> lib1.__interface__: c = get_contract(main, input_bundle=input_bundle) assert c.bar() == c.address + + +def test_intrinsic_interface_kws(env, make_input_bundle, get_contract): + value = 10**5 + lib1 = f""" +@external +@payable +def foo(a: address): + send(a, {value}) + """ + main = f""" +import lib1 + +exports: lib1.__interface__ + +@external +def bar(a: address): + extcall lib1.__at__(self).foo(a, value={value}) + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + c = get_contract(main, input_bundle=input_bundle) + env.set_balance(c.address, value) + original_balance = env.get_balance(env.deployer) + c.bar(env.deployer) + assert env.get_balance(env.deployer) == original_balance + value + + +def test_intrinsic_interface_defaults(env, make_input_bundle, get_contract): + lib1 = f""" +@external +@payable +def foo(i: uint256=1) -> uint256: + return i + """ + main = f""" +import lib1 + +exports: lib1.__interface__ + +@external +def bar() -> uint256: + return extcall lib1.__at__(self).foo() + """ + input_bundle = make_input_bundle({"lib1.vy": lib1}) + c = get_contract(main, input_bundle=input_bundle) + assert c.bar() == 1 From 4e282b460c1d48932409b38bdd93d1be1f10f341 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Wed, 13 Nov 2024 13:35:00 +0000 Subject: [PATCH 24/28] lint --- tests/functional/codegen/modules/test_interface_imports.py | 3 +++ tests/functional/codegen/test_interfaces.py | 6 +++--- tests/functional/syntax/modules/test_deploy_visibility.py | 1 + tests/functional/syntax/test_interfaces.py | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/functional/codegen/modules/test_interface_imports.py b/tests/functional/codegen/modules/test_interface_imports.py index 2f6d548987..af9f9b5e68 100644 --- a/tests/functional/codegen/modules/test_interface_imports.py +++ b/tests/functional/codegen/modules/test_interface_imports.py @@ -1,3 +1,6 @@ +import pytest + + def test_import_interface_types(make_input_bundle, get_contract): ifaces = """ interface IFoo: diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index 9e0c2ce0c3..31475a3bc0 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -788,7 +788,7 @@ def foo(): i: lib1.__interface__ -@external +@external def bar() -> lib1.__interface__: self.i = lib1.__at__(self) return self.i @@ -845,13 +845,13 @@ def bar(a: address): def test_intrinsic_interface_defaults(env, make_input_bundle, get_contract): - lib1 = f""" + lib1 = """ @external @payable def foo(i: uint256=1) -> uint256: return i """ - main = f""" + main = """ import lib1 exports: lib1.__interface__ diff --git a/tests/functional/syntax/modules/test_deploy_visibility.py b/tests/functional/syntax/modules/test_deploy_visibility.py index 4eb107708d..c908d4adae 100644 --- a/tests/functional/syntax/modules/test_deploy_visibility.py +++ b/tests/functional/syntax/modules/test_deploy_visibility.py @@ -26,6 +26,7 @@ def foo(): assert e.value.message == "Cannot call an @deploy function from an @external function!" + @pytest.mark.parametrize("interface_syntax", ["__interface__", "__at__"]) def test_module_interface_init(make_input_bundle, tmp_path, interface_syntax): lib1 = """ diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index 7a177ad3d4..0655ac6a6f 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -617,5 +617,6 @@ def bar(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) + # TODO make the exception more precise once fixed with pytest.raises(Exception): compiler.compile_code(main, input_bundle=input_bundle) From 341d4b63b34a76b5eb44138d8384f88f8c311856 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 20 Nov 2024 11:19:39 +0100 Subject: [PATCH 25/28] mark xfail --- tests/functional/syntax/test_interfaces.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index 0655ac6a6f..fbcbf51a70 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -600,6 +600,7 @@ def bar(): compiler.compile_code(main, input_bundle=input_bundle) +@pytest.mark.xfail def test_intrinsic_interfaces_default_function(make_input_bundle, get_contract): lib1 = """ @external From 9b298062a2070fdd0bfc35a2ace9ac7a293e44f9 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 25 Nov 2024 15:30:29 +0100 Subject: [PATCH 26/28] fix lint --- tests/functional/syntax/test_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index fbcbf51a70..eebfe61586 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -619,5 +619,5 @@ def bar(): input_bundle = make_input_bundle({"lib1.vy": lib1}) # TODO make the exception more precise once fixed - with pytest.raises(Exception): + with pytest.raises(): compiler.compile_code(main, input_bundle=input_bundle) From 4c463ac4d699d8ff9c8c53b24836e624841dfd00 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 25 Nov 2024 15:32:14 +0100 Subject: [PATCH 27/28] leave a linter hint --- tests/functional/syntax/test_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index eebfe61586..baf0c73c30 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -619,5 +619,5 @@ def bar(): input_bundle = make_input_bundle({"lib1.vy": lib1}) # TODO make the exception more precise once fixed - with pytest.raises(): + with pytest.raises(Exception): # noqa: B017 compiler.compile_code(main, input_bundle=input_bundle) From 307d70e05b1b35595e8293a2b525de3a56f481c0 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 25 Nov 2024 17:55:27 +0100 Subject: [PATCH 28/28] add docs for `module.__at__` --- docs/using-modules.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/using-modules.rst b/docs/using-modules.rst index 7d63eb6617..4400a8dfa8 100644 --- a/docs/using-modules.rst +++ b/docs/using-modules.rst @@ -62,6 +62,21 @@ The ``_times_two()`` helper function in the above module can be immediately used The other functions cannot be used yet, because they touch the ``ownable`` module's state. There are two ways to declare a module so that its state can be used. +Using a module as an interface +============================== + +A module can be used as an interface with the ``__at__`` syntax. + +.. code-block:: vyper + + import ownable + + an_ownable: ownable.__interface__ + + def call_ownable(addr: address): + self.an_ownable = ownable.__at__(addr) + self.an_ownable.transfer_ownership(...) + Initializing a module =====================