From 90747ef3ae18eaa2046b40c4f2e3ed49d7300504 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 24 Nov 2022 16:20:32 +0800 Subject: [PATCH 01/26] add blockhash to local validation --- vyper/semantics/validation/local.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py index 0217c28cc8..74cf5cb4b7 100644 --- a/vyper/semantics/validation/local.py +++ b/vyper/semantics/validation/local.py @@ -202,6 +202,10 @@ def __init__( ] node_list.extend(standalone_self) # type: ignore + # Add references to builtin functions reading from the chain's state + builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": "blockhash"}) + node_list.extend(builtin_fns) + for node in node_list: t = node._metadata.get("type") if isinstance(t, ContractFunction) and t.mutability == StateMutability.PURE: From 393dfed5edbfcd20f324ba12339ad8a7c5eef6fa Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 24 Nov 2022 16:23:20 +0800 Subject: [PATCH 02/26] add test --- tests/parser/features/decorators/test_pure.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/parser/features/decorators/test_pure.py b/tests/parser/features/decorators/test_pure.py index a587bc5856..efecbba3d3 100644 --- a/tests/parser/features/decorators/test_pure.py +++ b/tests/parser/features/decorators/test_pure.py @@ -129,3 +129,17 @@ def foo() -> uint256: ), FunctionDeclarationException, ) + + +def test_invalid_builtin(get_contract, assert_compile_failed): + assert_compile_failed( + lambda: get_contract( + """ +@external +@pure +def foo(x: uint256)-> bytes32: + return blockhash(x) + """ + ), + StateAccessViolation, + ) From 3d303c781c32e2559d98429077ef85412ac82728 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 24 Nov 2022 16:30:38 +0800 Subject: [PATCH 03/26] fix mypy lint --- vyper/semantics/validation/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py index 74cf5cb4b7..c6c6a67493 100644 --- a/vyper/semantics/validation/local.py +++ b/vyper/semantics/validation/local.py @@ -204,7 +204,7 @@ def __init__( # Add references to builtin functions reading from the chain's state builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": "blockhash"}) - node_list.extend(builtin_fns) + node_list.extend(builtin_fns) # type: ignore for node in node_list: t = node._metadata.get("type") From 439bb782d337ce919e87e2618aa39d373e2c0090 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 27 Nov 2022 14:32:42 +0800 Subject: [PATCH 04/26] use StateMutability --- vyper/builtin_functions/functions.py | 2 ++ vyper/builtin_functions/signatures.py | 2 ++ vyper/semantics/validation/local.py | 10 ++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/vyper/builtin_functions/functions.py b/vyper/builtin_functions/functions.py index ee3863c8ae..23e2a912c6 100644 --- a/vyper/builtin_functions/functions.py +++ b/vyper/builtin_functions/functions.py @@ -82,6 +82,7 @@ UnsignedIntegerAbstractType, ) from vyper.semantics.types.bases import DataLocation +from vyper.semantics.types.function import StateMutability from vyper.semantics.types.utils import KwargSettings, TypeTypeDefinition, get_type_from_annotation from vyper.semantics.types.value.address import AddressDefinition from vyper.semantics.types.value.array_value import ( @@ -1293,6 +1294,7 @@ class BlockHash(BuiltinFunction): _id = "blockhash" _inputs = [("block_num", Uint256Definition())] _return_type = Bytes32Definition() + mutability = StateMutability.VIEW @process_inputs def build_IR(self, expr, args, kwargs, contact): diff --git a/vyper/builtin_functions/signatures.py b/vyper/builtin_functions/signatures.py index d9a1c0c087..56a2a0ef6d 100644 --- a/vyper/builtin_functions/signatures.py +++ b/vyper/builtin_functions/signatures.py @@ -14,6 +14,7 @@ StructDefinition, ValueTypeDefinition, ) +from vyper.semantics.types.function import StateMutability from vyper.semantics.types.utils import KwargSettings, TypeTypeDefinition, get_type_from_annotation from vyper.semantics.validation.utils import get_exact_type_from_node, validate_expected_type @@ -87,6 +88,7 @@ class BuiltinFunction: _has_varargs = False _kwargs: Dict[str, KwargSettings] = {} + mutability = StateMutability.PURE # helper function to deal with TYPE_DEFINITIONs def _validate_single(self, arg, expected_type): diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py index c6c6a67493..597956e1d8 100644 --- a/vyper/semantics/validation/local.py +++ b/vyper/semantics/validation/local.py @@ -203,14 +203,20 @@ def __init__( node_list.extend(standalone_self) # type: ignore # Add references to builtin functions reading from the chain's state - builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": "blockhash"}) + # TODO Fix circular import + from vyper.builtin_functions.functions import BUILTIN_FUNCTIONS + builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": set(BUILTIN_FUNCTIONS)}) + node_list.extend(builtin_fns) # type: ignore for node in node_list: t = node._metadata.get("type") - if isinstance(t, ContractFunction) and t.mutability == StateMutability.PURE: + # TODO Fix circular import + from vyper.builtin_functions.signatures import BuiltinFunction + if isinstance(t, (BuiltinFunction, ContractFunction)) and t.mutability == StateMutability.PURE: # allowed continue + raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], From 5fadb3bd626b94d01167757bdc3f80ea3285b22a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 27 Nov 2022 14:36:14 +0800 Subject: [PATCH 05/26] fix lint --- vyper/semantics/validation/local.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py index 597956e1d8..8e8a9ebda4 100644 --- a/vyper/semantics/validation/local.py +++ b/vyper/semantics/validation/local.py @@ -205,6 +205,7 @@ def __init__( # Add references to builtin functions reading from the chain's state # TODO Fix circular import from vyper.builtin_functions.functions import BUILTIN_FUNCTIONS + builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": set(BUILTIN_FUNCTIONS)}) node_list.extend(builtin_fns) # type: ignore @@ -213,7 +214,11 @@ def __init__( t = node._metadata.get("type") # TODO Fix circular import from vyper.builtin_functions.signatures import BuiltinFunction - if isinstance(t, (BuiltinFunction, ContractFunction)) and t.mutability == StateMutability.PURE: + + if ( + isinstance(t, (BuiltinFunction, ContractFunction)) + and t.mutability == StateMutability.PURE + ): # allowed continue From aa82067ac61f3240121d3aaca50f026b9d299278 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:20:07 +0800 Subject: [PATCH 06/26] fix merge conflict --- vyper/builtins/_signatures.py | 1 + vyper/builtins/functions.py | 2 +- vyper/semantics/analysis/local.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index a0d53e0420..c14013a494 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -7,6 +7,7 @@ from vyper.codegen.ir_node import IRnode from vyper.codegen.types.convert import new_type_to_old_type from vyper.exceptions import CompilerPanic, TypeMismatch +from vyper.semantics.analysis.base import StateMutability from vyper.semantics.analysis.utils import get_exact_type_from_node, validate_expected_type from vyper.semantics.types import TYPE_T, KwargSettings, VyperType from vyper.semantics.types.utils import type_from_annotation diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 6ff392648b..2f6a7ee5d6 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -64,7 +64,7 @@ UnfoldableNode, ZeroDivisionException, ) -from vyper.semantics.analysis.base import VarInfo +from vyper.semantics.analysis.base import StateMutability, VarInfo from vyper.semantics.analysis.utils import ( get_common_types, get_exact_type_from_node, diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index a46926b690..e0f33e2b2f 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -202,7 +202,7 @@ def __init__( # Add references to builtin functions reading from the chain's state # TODO Fix circular import - from vyper.builtin_functions.functions import BUILTIN_FUNCTIONS + from vyper.builtins.functions import BUILTIN_FUNCTIONS builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": set(BUILTIN_FUNCTIONS)}) @@ -211,7 +211,7 @@ def __init__( for node in node_list: t = node._metadata.get("type") # TODO Fix circular import - from vyper.builtin_functions.signatures import BuiltinFunction + from vyper.builtins.signatures import BuiltinFunction if ( isinstance(t, (BuiltinFunction, ContractFunction)) From cccd707117ab3da3ea11e08f196b7859bc021239 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:13 +0800 Subject: [PATCH 07/26] fix import --- vyper/semantics/analysis/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index e0f33e2b2f..ad601c29eb 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -211,7 +211,7 @@ def __init__( for node in node_list: t = node._metadata.get("type") # TODO Fix circular import - from vyper.builtins.signatures import BuiltinFunction + from vyper.builtins._signatures import BuiltinFunction if ( isinstance(t, (BuiltinFunction, ContractFunction)) From a0e80ad6ca7ead63f3fbf6941a9a392b53f65c4e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 13:16:43 +0800 Subject: [PATCH 08/26] clean up imports --- vyper/semantics/analysis/local.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index f6c8ea6a11..35ff0c2cea 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -3,6 +3,7 @@ from vyper import ast as vy_ast from vyper.ast.metadata import NodeMetadata from vyper.ast.validation import validate_call_args +from vyper.builtins._signatures import BuiltinFunction from vyper.exceptions import ( ExceptionList, FunctionDeclarationException, @@ -213,9 +214,6 @@ def __init__( for node in node_list: t = node._metadata.get("type") - # TODO Fix circular import - from vyper.builtins._signatures import BuiltinFunction - if ( isinstance(t, (BuiltinFunction, ContractFunctionT)) and t.mutability == StateMutability.PURE From b3e4b62426b0573ec184791f5b1d20c743d2689b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 13:19:04 +0800 Subject: [PATCH 09/26] undo blank line --- vyper/semantics/analysis/local.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 35ff0c2cea..d85d27e586 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -220,7 +220,6 @@ def __init__( ): # allowed continue - raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], From 79e1674bfa4e03cb91e80834cb1407fb87f1b589 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 13:38:02 +0800 Subject: [PATCH 10/26] filter for nonpure builtins from namespace --- vyper/semantics/analysis/local.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index d85d27e586..f304fefac2 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -205,10 +205,16 @@ def __init__( node_list.extend(standalone_self) # type: ignore # Add references to builtin functions reading from the chain's state - # TODO Fix circular import - from vyper.builtins.functions import BUILTIN_FUNCTIONS - - builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": set(BUILTIN_FUNCTIONS)}) + non_pure_builtin_fns = [ + k + for k, v in self.namespace.items() + if ( + isinstance(v, VarInfo) + and isinstance(v.typ, BuiltinFunction) + and v.typ.mutability > StateMutability.PURE + ) + ] + builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": non_pure_builtin_fns}) node_list.extend(builtin_fns) # type: ignore From f6f2e91e4a5ec1174cde92018aaec4bbc874a8d0 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 15:33:07 +0800 Subject: [PATCH 11/26] fetch all call nodes --- vyper/semantics/analysis/local.py | 42 +++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index f304fefac2..bfc37b3b55 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -3,7 +3,6 @@ from vyper import ast as vy_ast from vyper.ast.metadata import NodeMetadata from vyper.ast.validation import validate_call_args -from vyper.builtins._signatures import BuiltinFunction from vyper.exceptions import ( ExceptionList, FunctionDeclarationException, @@ -41,6 +40,7 @@ EventT, HashMapT, IntegerT, + InterfaceT, SArrayT, StringT, StructT, @@ -204,32 +204,42 @@ def __init__( ] node_list.extend(standalone_self) # type: ignore - # Add references to builtin functions reading from the chain's state - non_pure_builtin_fns = [ - k - for k, v in self.namespace.items() - if ( - isinstance(v, VarInfo) - and isinstance(v.typ, BuiltinFunction) - and v.typ.mutability > StateMutability.PURE - ) + # Add all calls except structs + filtered_call_nodes = [ + c.func + for c in fn_node.get_descendants(vy_ast.Call) + if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) ] - builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": non_pure_builtin_fns}) - node_list.extend(builtin_fns) # type: ignore + node_list.extend(filtered_call_nodes) for node in node_list: t = node._metadata.get("type") - if ( - isinstance(t, (BuiltinFunction, ContractFunctionT)) - and t.mutability == StateMutability.PURE - ): + # skip structs and interface instantiation + if isinstance(t, StructT) or is_type_t(t, InterfaceT): + continue + + # skip ContractFunctionT and BuiltinFunction with mutability set to pure + if hasattr(t, "mutability") and t.mutability == StateMutability.PURE: # allowed continue + raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], ) + + # collect all Call's name nodes except structs + node_list = [ + c.func + for c in fn_node.get_descendants(vy_ast.Call) + if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) + ] + self_references = fn_node.get_descendants(vy_ast.Name, {"id": "self"}) + standalone_self = [ + n for n in self_references if not isinstance(n.get_ancestor(), vy_ast.Attribute) + ] + node_list.extend(standalone_self) # type: ignore if self.func.mutability is not StateMutability.PAYABLE: node_list = fn_node.get_descendants( vy_ast.Attribute, {"value.id": "msg", "attr": "value"} From a5b1924af07309145d34507c6f8e5b4f40ecd034 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 15:34:20 +0800 Subject: [PATCH 12/26] clean up --- vyper/semantics/analysis/local.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index bfc37b3b55..5dddad6ed2 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -210,7 +210,6 @@ def __init__( for c in fn_node.get_descendants(vy_ast.Call) if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) ] - node_list.extend(filtered_call_nodes) for node in node_list: @@ -228,18 +227,6 @@ def __init__( "not allowed to query contract or environment variables in pure functions", node_list[0], ) - - # collect all Call's name nodes except structs - node_list = [ - c.func - for c in fn_node.get_descendants(vy_ast.Call) - if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) - ] - self_references = fn_node.get_descendants(vy_ast.Name, {"id": "self"}) - standalone_self = [ - n for n in self_references if not isinstance(n.get_ancestor(), vy_ast.Attribute) - ] - node_list.extend(standalone_self) # type: ignore if self.func.mutability is not StateMutability.PAYABLE: node_list = fn_node.get_descendants( vy_ast.Attribute, {"value.id": "msg", "attr": "value"} From b754a1b069e344ab9404db65485461c60849a4f3 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 15:34:59 +0800 Subject: [PATCH 13/26] undo blank line --- vyper/semantics/analysis/local.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 5dddad6ed2..104b5b0bf7 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -222,7 +222,6 @@ def __init__( if hasattr(t, "mutability") and t.mutability == StateMutability.PURE: # allowed continue - raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], From eff8438658c684489a65188a85b15c2a44f72bac Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 15:37:12 +0800 Subject: [PATCH 14/26] add interface test --- tests/parser/features/decorators/test_pure.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/parser/features/decorators/test_pure.py b/tests/parser/features/decorators/test_pure.py index efecbba3d3..d193c8838f 100644 --- a/tests/parser/features/decorators/test_pure.py +++ b/tests/parser/features/decorators/test_pure.py @@ -143,3 +143,21 @@ def foo(x: uint256)-> bytes32: ), StateAccessViolation, ) + + +def test_invalid_interface(get_contract, assert_compile_failed): + assert_compile_failed( + lambda: get_contract( + """ +interface Foo: + def foo() -> uint256: payable + + +@external +@pure +def bar(a: address) -> uint256: + return Foo(a).foo() + """ + ), + StateAccessViolation, + ) \ No newline at end of file From 035a91bbafc3db582d5c91984d412f46d4f3186e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 18 May 2023 19:21:22 +0800 Subject: [PATCH 15/26] fix lint --- tests/parser/features/decorators/test_pure.py | 2 +- vyper/semantics/analysis/local.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/parser/features/decorators/test_pure.py b/tests/parser/features/decorators/test_pure.py index d193c8838f..0bb6453217 100644 --- a/tests/parser/features/decorators/test_pure.py +++ b/tests/parser/features/decorators/test_pure.py @@ -160,4 +160,4 @@ def bar(a: address) -> uint256: """ ), StateAccessViolation, - ) \ No newline at end of file + ) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 104b5b0bf7..f98da0e5f3 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -210,7 +210,7 @@ def __init__( for c in fn_node.get_descendants(vy_ast.Call) if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) ] - node_list.extend(filtered_call_nodes) + node_list.extend(filtered_call_nodes) # type: ignore for node in node_list: t = node._metadata.get("type") From f6c95b23f217809007f1d86c92992a748637d406 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:37:49 +0800 Subject: [PATCH 16/26] revert merge conflict --- vyper/builtins/functions.py | 4 ++-- vyper/semantics/analysis/local.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 1dac583ee6..c1290b77fc 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -49,7 +49,7 @@ UnfoldableNode, ZeroDivisionException, ) -from vyper.semantics.analysis.base import Modifiability, VarInfo +from vyper.semantics.analysis.base import Modifiability, StateMutability, VarInfo from vyper.semantics.analysis.utils import ( get_common_types, get_exact_type_from_node, @@ -1247,7 +1247,7 @@ class BlockHash(BuiltinFunctionT): _id = "blockhash" _inputs = [("block_num", UINT256_T)] _return_type = BYTES32_T - _modifiability = Modifiability.RUNTIME_CONSTANT + mutability = StateMutability.VIEW @process_inputs def build_IR(self, expr, args, kwargs, contact): diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 0ce966d1c6..88cf153406 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -743,6 +743,9 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: self.visit(arg, arg_type) else: # builtin functions + if self.function_analyzer: + self._check_call_mutability(func_type.mutability) # type: ignore + arg_types = func_type.infer_arg_types(node, expected_return_typ=typ) # type: ignore for arg, arg_type in zip(node.args, arg_types): self.visit(arg, arg_type) From 19baa5dc695f211113d2562ceebca486c1f1c46b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:59:32 +0800 Subject: [PATCH 17/26] some fixes --- vyper/builtins/_signatures.py | 3 ++- vyper/semantics/analysis/local.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/builtins/_signatures.py b/vyper/builtins/_signatures.py index 6e6cf4c662..29a35e63e3 100644 --- a/vyper/builtins/_signatures.py +++ b/vyper/builtins/_signatures.py @@ -6,7 +6,7 @@ from vyper.codegen.expr import Expr from vyper.codegen.ir_node import IRnode from vyper.exceptions import CompilerPanic, TypeMismatch, UnfoldableNode -from vyper.semantics.analysis.base import Modifiability +from vyper.semantics.analysis.base import Modifiability, StateMutability from vyper.semantics.analysis.utils import ( check_modifiability, get_exact_type_from_node, @@ -87,6 +87,7 @@ class BuiltinFunctionT(VyperType): _return_type: Optional[VyperType] = None _equality_attrs = ("_id",) _is_terminus = False + mutability: StateMutability = StateMutability.PURE @property def modifiability(self): diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 88cf153406..3fb66f8516 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -742,8 +742,8 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: for arg, arg_type in zip(node.args, func_type.arg_types): self.visit(arg, arg_type) else: - # builtin functions - if self.function_analyzer: + # builtin functions and interfaces + if self.function_analyzer and hasattr(func_type, "mutability"): self._check_call_mutability(func_type.mutability) # type: ignore arg_types = func_type.infer_arg_types(node, expected_return_typ=typ) # type: ignore From a33c7a8646300c4e5728365e541224bac735aeac Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:36:31 +0800 Subject: [PATCH 18/26] move raw_call check to fetch_call_return --- vyper/builtins/functions.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index c1290b77fc..711ff01726 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1061,6 +1061,25 @@ def fetch_call_return(self, node): kwargz = {i.arg: i.value for i in node.keywords} + delegate_call = kwargz.get("is_delegate_call") + static_call = kwargz.get("is_static_call") + if delegate_call and static_call: + raise ArgumentException( + "Call may use one of `is_delegate_call` or `is_static_call`, not both" + ) + + value = kwargz.get("value") + if (delegate_call or static_call) and value is not None: + raise ArgumentException("value= may not be passed for static or delegate calls!") + + fn_node = node.get_ancestor(vy_ast.FunctionDef) + fn_type = fn_node._metadata["func_type"] + if not static_call and not fn_type.is_mutable: + raise StateAccessViolation( + f"Cannot make modifying calls from {fn_type.mutability}," + " use `is_static_call=True` to perform this action" + ) + outsize = kwargz.get("max_outsize") if outsize is not None: outsize = outsize.get_folded_value() @@ -1106,20 +1125,6 @@ def build_IR(self, expr, args, kwargs, context): kwargs["revert_on_failure"], ) - if delegate_call and static_call: - raise ArgumentException( - "Call may use one of `is_delegate_call` or `is_static_call`, not both" - ) - - if (delegate_call or static_call) and value.value != 0: - raise ArgumentException("value= may not be passed for static or delegate calls!") - - if not static_call and context.is_constant(): - raise StateAccessViolation( - f"Cannot make modifying calls from {context.pp_constancy()}," - " use `is_static_call=True` to perform this action" - ) - if data.value == "~calldata": call_ir = ["with", "mem_ofst", "msize"] args_ofst = ["seq", ["calldatacopy", "mem_ofst", 0, "calldatasize"], "mem_ofst"] From bf26d2eccd5f68880804859c3abd47a35e067f15 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:36:44 +0800 Subject: [PATCH 19/26] fix lint --- vyper/semantics/analysis/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index 3fb66f8516..c86714394c 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -744,7 +744,7 @@ def visit_Call(self, node: vy_ast.Call, typ: VyperType) -> None: else: # builtin functions and interfaces if self.function_analyzer and hasattr(func_type, "mutability"): - self._check_call_mutability(func_type.mutability) # type: ignore + self._check_call_mutability(func_type.mutability) # type: ignore arg_types = func_type.infer_arg_types(node, expected_return_typ=typ) # type: ignore for arg, arg_type in zip(node.args, arg_types): From c3f2bf6be3ef4e263ddb9cd1b02297f16d445b03 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:04:24 +0800 Subject: [PATCH 20/26] fix test --- tests/functional/codegen/features/decorators/test_pure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/codegen/features/decorators/test_pure.py b/tests/functional/codegen/features/decorators/test_pure.py index 723f160d35..1c5a3a12a7 100644 --- a/tests/functional/codegen/features/decorators/test_pure.py +++ b/tests/functional/codegen/features/decorators/test_pure.py @@ -156,7 +156,7 @@ def foo() -> uint256: payable @external @pure def bar(a: address) -> uint256: - return Foo(a).foo() + return extcall Foo(a).foo() """ ), StateAccessViolation, From 3931761c7cbc1f5e3192556c7c20d2be9a89da6e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:22:47 +0800 Subject: [PATCH 21/26] modify tests --- .../test_external_contract_calls.py | 18 ++++++----- .../codegen/features/decorators/test_pure.py | 31 +++++-------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/tests/functional/codegen/calling_convention/test_external_contract_calls.py b/tests/functional/codegen/calling_convention/test_external_contract_calls.py index 0f5f915a48..af78a2ca45 100644 --- a/tests/functional/codegen/calling_convention/test_external_contract_calls.py +++ b/tests/functional/codegen/calling_convention/test_external_contract_calls.py @@ -900,13 +900,17 @@ def set_lucky(arg1: address, arg2: int128): print("Successfully executed an external contract call state change") -def test_constant_external_contract_call_cannot_change_state(): - c = """ +@pytest.mark.parametrize("state_modifying_decorator", ("payable", "nonpayable")) +@pytest.mark.parametrize("non_modifying_decorator", ("pure", "view")) +def test_constant_external_contract_call_cannot_change_state( + state_modifying_decorator, non_modifying_decorator +): + c = f""" interface Foo: - def set_lucky(_lucky: int128) -> int128: nonpayable + def set_lucky(_lucky: int128) -> int128: {state_modifying_decorator} @external -@view +@{non_modifying_decorator} def set_lucky_stmt(arg1: address, arg2: int128): extcall Foo(arg1).set_lucky(arg2) """ @@ -914,11 +918,11 @@ def set_lucky_stmt(arg1: address, arg2: int128): with pytest.raises(StateAccessViolation): compile_code(c) - c2 = """ + c2 = f""" interface Foo: - def set_lucky(_lucky: int128) -> int128: nonpayable + def set_lucky(_lucky: int128) -> int128: {state_modifying_decorator} @external -@view +@{non_modifying_decorator} def set_lucky_expr(arg1: address, arg2: int128) -> int128: return extcall Foo(arg1).set_lucky(arg2) """ diff --git a/tests/functional/codegen/features/decorators/test_pure.py b/tests/functional/codegen/features/decorators/test_pure.py index 1c5a3a12a7..170a3d7a3c 100644 --- a/tests/functional/codegen/features/decorators/test_pure.py +++ b/tests/functional/codegen/features/decorators/test_pure.py @@ -1,3 +1,6 @@ +import pytest + +from vyper import compile_code from vyper.exceptions import FunctionDeclarationException, StateAccessViolation @@ -131,33 +134,13 @@ def foo() -> uint256: ) -def test_invalid_builtin(get_contract, assert_compile_failed): - assert_compile_failed( - lambda: get_contract( - """ +def test_invalid_builtin(get_contract): + code = """ @external @pure def foo(x: uint256)-> bytes32: return blockhash(x) """ - ), - StateAccessViolation, - ) - - -def test_invalid_interface(get_contract, assert_compile_failed): - assert_compile_failed( - lambda: get_contract( - """ -interface Foo: - def foo() -> uint256: payable - -@external -@pure -def bar(a: address) -> uint256: - return extcall Foo(a).foo() - """ - ), - StateAccessViolation, - ) + with pytest.raises(StateAccessViolation): + compile_code(code) From 189554963236702c44bbda58627159d814f59a05 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:26:28 +0800 Subject: [PATCH 22/26] add default value --- vyper/builtins/functions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index a42d782318..505dbaac49 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1061,23 +1061,24 @@ def fetch_call_return(self, node): kwargz = {i.arg: i.value for i in node.keywords} - delegate_call = kwargz.get("is_delegate_call") - static_call = kwargz.get("is_static_call") + delegate_call = kwargz.get("is_delegate_call", False) + static_call = kwargz.get("is_static_call", False) if delegate_call and static_call: raise ArgumentException( - "Call may use one of `is_delegate_call` or `is_static_call`, not both" + "Call may use one of `is_delegate_call` or `is_static_call`, not both", node ) value = kwargz.get("value") if (delegate_call or static_call) and value is not None: - raise ArgumentException("value= may not be passed for static or delegate calls!") + raise ArgumentException("value= may not be passed for static or delegate calls!", node) fn_node = node.get_ancestor(vy_ast.FunctionDef) fn_type = fn_node._metadata["func_type"] if not static_call and not fn_type.is_mutable: raise StateAccessViolation( f"Cannot make modifying calls from {fn_type.mutability}," - " use `is_static_call=True` to perform this action" + " use `is_static_call=True` to perform this action", + node, ) outsize = kwargz.get("max_outsize") From dad2c4657227778cdc27e2834ac5c10d45482ff9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:55:48 +0800 Subject: [PATCH 23/26] apply bts suggestion --- .../test_external_contract_calls.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional/codegen/calling_convention/test_external_contract_calls.py b/tests/functional/codegen/calling_convention/test_external_contract_calls.py index af78a2ca45..e8863c3612 100644 --- a/tests/functional/codegen/calling_convention/test_external_contract_calls.py +++ b/tests/functional/codegen/calling_convention/test_external_contract_calls.py @@ -900,17 +900,17 @@ def set_lucky(arg1: address, arg2: int128): print("Successfully executed an external contract call state change") -@pytest.mark.parametrize("state_modifying_decorator", ("payable", "nonpayable")) -@pytest.mark.parametrize("non_modifying_decorator", ("pure", "view")) +@pytest.mark.parametrize("modifying", ("payable", "nonpayable")) +@pytest.mark.parametrize("constant", ("pure", "view")) def test_constant_external_contract_call_cannot_change_state( - state_modifying_decorator, non_modifying_decorator + modifying, constant ): c = f""" interface Foo: - def set_lucky(_lucky: int128) -> int128: {state_modifying_decorator} + def set_lucky(_lucky: int128) -> int128: {modifying} @external -@{non_modifying_decorator} +@{constant} def set_lucky_stmt(arg1: address, arg2: int128): extcall Foo(arg1).set_lucky(arg2) """ @@ -920,9 +920,9 @@ def set_lucky_stmt(arg1: address, arg2: int128): c2 = f""" interface Foo: - def set_lucky(_lucky: int128) -> int128: {state_modifying_decorator} + def set_lucky(_lucky: int128) -> int128: {modifying} @external -@{non_modifying_decorator} +@{constant} def set_lucky_expr(arg1: address, arg2: int128) -> int128: return extcall Foo(arg1).set_lucky(arg2) """ From ebfe00394330116aea61b990addc8b53c7a64852 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:57:25 +0800 Subject: [PATCH 24/26] revert raw call changes --- vyper/builtins/functions.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 505dbaac49..f22437c2bb 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1061,26 +1061,6 @@ def fetch_call_return(self, node): kwargz = {i.arg: i.value for i in node.keywords} - delegate_call = kwargz.get("is_delegate_call", False) - static_call = kwargz.get("is_static_call", False) - if delegate_call and static_call: - raise ArgumentException( - "Call may use one of `is_delegate_call` or `is_static_call`, not both", node - ) - - value = kwargz.get("value") - if (delegate_call or static_call) and value is not None: - raise ArgumentException("value= may not be passed for static or delegate calls!", node) - - fn_node = node.get_ancestor(vy_ast.FunctionDef) - fn_type = fn_node._metadata["func_type"] - if not static_call and not fn_type.is_mutable: - raise StateAccessViolation( - f"Cannot make modifying calls from {fn_type.mutability}," - " use `is_static_call=True` to perform this action", - node, - ) - outsize = kwargz.get("max_outsize") if outsize is not None: outsize = outsize.get_folded_value() @@ -1126,6 +1106,20 @@ def build_IR(self, expr, args, kwargs, context): kwargs["revert_on_failure"], ) + if delegate_call and static_call: + raise ArgumentException( + "Call may use one of `is_delegate_call` or `is_static_call`, not both" + ) + + if (delegate_call or static_call) and value.value != 0: + raise ArgumentException("value= may not be passed for static or delegate calls!") + + if not static_call and context.is_constant(): + raise StateAccessViolation( + f"Cannot make modifying calls from {context.pp_constancy()}," + " use `is_static_call=True` to perform this action" + ) + if data.value == "~calldata": call_ir = ["with", "mem_ofst", "msize"] args_ofst = ["seq", ["calldatacopy", "mem_ofst", 0, "calldatasize"], "mem_ofst"] From 47099362a10ba72393001f4ef10f5e7746c536fd Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:57:29 +0800 Subject: [PATCH 25/26] fix lint --- .../calling_convention/test_external_contract_calls.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/functional/codegen/calling_convention/test_external_contract_calls.py b/tests/functional/codegen/calling_convention/test_external_contract_calls.py index e8863c3612..dfa84f6475 100644 --- a/tests/functional/codegen/calling_convention/test_external_contract_calls.py +++ b/tests/functional/codegen/calling_convention/test_external_contract_calls.py @@ -902,9 +902,7 @@ def set_lucky(arg1: address, arg2: int128): @pytest.mark.parametrize("modifying", ("payable", "nonpayable")) @pytest.mark.parametrize("constant", ("pure", "view")) -def test_constant_external_contract_call_cannot_change_state( - modifying, constant -): +def test_constant_external_contract_call_cannot_change_state(modifying, constant): c = f""" interface Foo: def set_lucky(_lucky: int128) -> int128: {modifying} From 861090803154b7f5a8172aa35115c89b0920df8a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:04:53 +0800 Subject: [PATCH 26/26] add blobhash --- .../codegen/features/decorators/test_pure.py | 12 +++++++++++- vyper/builtins/functions.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/features/decorators/test_pure.py b/tests/functional/codegen/features/decorators/test_pure.py index 73cebe6eb8..7a1c44957c 100644 --- a/tests/functional/codegen/features/decorators/test_pure.py +++ b/tests/functional/codegen/features/decorators/test_pure.py @@ -200,7 +200,7 @@ def foo() -> uint256: compile_code(code) -def test_invalid_builtin(get_contract): +def test_invalid_builtins(get_contract): code = """ @external @pure @@ -211,6 +211,16 @@ def foo(x: uint256)-> bytes32: with pytest.raises(StateAccessViolation): compile_code(code) + code = """ +@external +@pure +def foo(x: uint256)-> bytes32: + return blobhash(x) + """ + + with pytest.raises(StateAccessViolation): + compile_code(code) + @pytest.mark.requires_evm_version("cancun") def test_invalid_transient_access(): diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index bf345ee755..351ccabcb3 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -1218,6 +1218,7 @@ class BlobHash(BuiltinFunctionT): _id = "blobhash" _inputs = [("index", UINT256_T)] _return_type = BYTES32_T + mutability = StateMutability.VIEW @process_inputs def build_IR(self, expr, args, kwargs, contact):