From f4f3ea865cc320d65bd923014b051b4c94d40fac Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 11:08:37 -0400 Subject: [PATCH 01/56] chore: replace `is_immutable` -> `is_constant` Changed across codebase to prevent future naming confusion when the `immutable` keyword is added. --- vyper/semantics/environment.py | 6 +++--- vyper/semantics/types/bases.py | 22 ++++++++++----------- vyper/semantics/types/function.py | 10 +++++----- vyper/semantics/types/indexable/mapping.py | 4 ++-- vyper/semantics/types/indexable/sequence.py | 13 ++++++------ vyper/semantics/types/user/interface.py | 8 ++++---- vyper/semantics/types/user/struct.py | 8 ++++---- vyper/semantics/types/utils.py | 18 ++++++++--------- vyper/semantics/types/value/address.py | 8 ++++---- vyper/semantics/types/value/array_value.py | 8 ++++---- vyper/semantics/validation/local.py | 2 +- vyper/semantics/validation/module.py | 8 ++++---- 12 files changed, 57 insertions(+), 58 deletions(-) diff --git a/vyper/semantics/environment.py b/vyper/semantics/environment.py index 791d773cdc..89deaefbda 100644 --- a/vyper/semantics/environment.py +++ b/vyper/semantics/environment.py @@ -39,8 +39,8 @@ def get_constant_vars() -> Dict: """ result = {} for name, members in CONSTANT_ENVIRONMENT_VARS.items(): - members = {k: v(is_immutable=True) for k, v in members.items()} - result[name] = StructDefinition(name, members, is_immutable=True) + members = {k: v(is_constant=True) for k, v in members.items()} + result[name] = StructDefinition(name, members, is_constant=True) return result @@ -50,4 +50,4 @@ def get_mutable_vars() -> Dict: Get a dictionary of mutable environment variables (those that are modified during the course of contract execution, such as `self`). """ - return {name: type_(is_immutable=True) for name, type_ in MUTABLE_ENVIRONMENT_VARS.items()} + return {name: type_(is_constant=True) for name, type_ in MUTABLE_ENVIRONMENT_VARS.items()} diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index 7542f2a649..bfc6d42172 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -98,7 +98,7 @@ def from_annotation( cls, node: Union[vy_ast.Name, vy_ast.Call], location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> "BaseTypeDefinition": """ @@ -118,7 +118,7 @@ def from_annotation( raise StructureException("Invalid type assignment", node) if node.id != cls._id: raise UnexpectedValue("Node id does not match type name") - return cls._type(location, is_immutable, is_public) + return cls._type(location, is_constant, is_public) @classmethod def from_literal(cls, node: vy_ast.Constant) -> "BaseTypeDefinition": @@ -227,7 +227,7 @@ class BaseTypeDefinition: Object Attributes ----------------- - is_immutable : bool, optional + is_constant : bool, optional If `True`, the value of this object cannot be modified after assignment. size_in_bytes: int The number of bytes that are required to store this type. @@ -240,11 +240,11 @@ class BaseTypeDefinition: def __init__( self, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: self.location = location - self.is_immutable = is_immutable + self.is_constant = is_constant self.is_public = is_public @property @@ -426,7 +426,7 @@ def validate_modification(self, node: Union[vy_ast.Assign, vy_ast.AugAssign]) -> """ if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) - if self.is_immutable: + if self.is_constant: raise ImmutableViolation("Immutable value cannot be written to", node) if isinstance(node, vy_ast.AugAssign): self.validate_numeric_op(node) @@ -498,10 +498,10 @@ class MemberTypeDefinition(ValueTypeDefinition): def __init__( self, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: - super().__init__(location, is_immutable, is_public) + super().__init__(location, is_constant, is_public) self.members: OrderedDict = OrderedDict() def add_member(self, name: str, type_: BaseTypeDefinition) -> None: @@ -517,7 +517,7 @@ def get_member(self, key: str, node: vy_ast.VyperNode) -> BaseTypeDefinition: elif key in getattr(self, "_type_members", []): type_ = copy.deepcopy(self._type_members[key]) type_.location = self.location - type_.is_immutable = self.is_immutable + type_.is_constant = self.is_constant return type_ raise UnknownAttribute(f"{self} has no member '{key}'", node) @@ -545,10 +545,10 @@ def __init__( key_type: BaseTypeDefinition, _id: str, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: - super().__init__(location, is_immutable, is_public) + super().__init__(location, is_constant, is_public) self.value_type = value_type self.key_type = key_type self._id = _id diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 5cb4dc34c2..ba8a3038fc 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -104,7 +104,7 @@ def __init__( # A function definition type only exists while compiling DataLocation.UNSET, # A function definition type is immutable once created - is_immutable=True, + is_constant=True, # A function definition type is public if it's visibility is public is_public=(function_visibility == FunctionVisibility.EXTERNAL), ) @@ -141,17 +141,17 @@ def from_abi(cls, abi: Dict) -> "ContractFunction": arguments = OrderedDict() for item in abi["inputs"]: arguments[item["name"]] = get_type_from_abi( - item, location=DataLocation.CALLDATA, is_immutable=True + item, location=DataLocation.CALLDATA, is_constant=True ) return_type = None if len(abi["outputs"]) == 1: return_type = get_type_from_abi( - abi["outputs"][0], location=DataLocation.CALLDATA, is_immutable=True + abi["outputs"][0], location=DataLocation.CALLDATA, is_constant=True ) elif len(abi["outputs"]) > 1: return_type = TupleDefinition( tuple( - get_type_from_abi(i, location=DataLocation.CALLDATA, is_immutable=True) + get_type_from_abi(i, location=DataLocation.CALLDATA, is_constant=True) for i in abi["outputs"] ) ) @@ -319,7 +319,7 @@ def from_FunctionDef( raise ArgumentException(f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( - arg.annotation, location=DataLocation.CALLDATA, is_immutable=True + arg.annotation, location=DataLocation.CALLDATA, is_constant=True ) if value is not None: if not check_constant(value): diff --git a/vyper/semantics/types/indexable/mapping.py b/vyper/semantics/types/indexable/mapping.py index 9acb73f68a..4935e07fb0 100644 --- a/vyper/semantics/types/indexable/mapping.py +++ b/vyper/semantics/types/indexable/mapping.py @@ -34,7 +34,7 @@ def from_annotation( cls, node: Union[vy_ast.Name, vy_ast.Call, vy_ast.Subscript], location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> MappingDefinition: if ( @@ -56,6 +56,6 @@ def from_annotation( key_type, f"HashMap[{key_type}, {value_type}]", location, - is_immutable, + is_constant, is_public, ) diff --git a/vyper/semantics/types/indexable/sequence.py b/vyper/semantics/types/indexable/sequence.py index f6e451a466..59e3745cd9 100644 --- a/vyper/semantics/types/indexable/sequence.py +++ b/vyper/semantics/types/indexable/sequence.py @@ -24,7 +24,7 @@ def __init__( length: int, _id: str, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: if not 0 < length < 2 ** 256: @@ -34,7 +34,7 @@ def __init__( IntegerAbstractType(), # type: ignore _id, location=location, - is_immutable=is_immutable, + is_constant=is_constant, is_public=is_public, ) self.length = length @@ -60,11 +60,11 @@ def __init__( value_type: BaseTypeDefinition, length: int, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: super().__init__( - value_type, length, f"{value_type}[{length}]", location, is_immutable, is_public + value_type, length, f"{value_type}[{length}]", location, is_constant, is_public ) def __repr__(self): @@ -111,15 +111,14 @@ class TupleDefinition(_SequenceDefinition): def __init__(self, value_type: Tuple[BaseTypeDefinition, ...]) -> None: # always use the most restrictive location re: modification location = sorted((i.location for i in value_type), key=lambda k: k.value)[-1] - is_immutable = next((True for i in value_type if getattr(i, "is_immutable", None)), False) - + is_constant = next((True for i in value_type if getattr(i, "is_constant", None)), False) super().__init__( # TODO fix the typing on value_type value_type, # type: ignore len(value_type), f"{value_type}", location, - is_immutable, + is_constant, ) def __repr__(self): diff --git a/vyper/semantics/types/user/interface.py b/vyper/semantics/types/user/interface.py index bb7dbbf41f..508aaa4b44 100644 --- a/vyper/semantics/types/user/interface.py +++ b/vyper/semantics/types/user/interface.py @@ -22,11 +22,11 @@ def __init__( _id: str, members: OrderedDict, location: DataLocation = DataLocation.MEMORY, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: self._id = _id - super().__init__(location, is_immutable, is_public) + super().__init__(location, is_constant, is_public) for key, type_ in members.items(): self.add_member(key, type_) @@ -54,14 +54,14 @@ def from_annotation( self, node: vy_ast.VyperNode, location: DataLocation = DataLocation.MEMORY, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> InterfaceDefinition: if not isinstance(node, vy_ast.Name): raise StructureException("Invalid type assignment", node) - return InterfaceDefinition(self._id, self.members, location, is_immutable, is_public) + return InterfaceDefinition(self._id, self.members, location, is_constant, is_public) def fetch_call_return(self, node: vy_ast.Call) -> InterfaceDefinition: validate_call_args(node, 1) diff --git a/vyper/semantics/types/user/struct.py b/vyper/semantics/types/user/struct.py index 01b67cb0a6..7f80c896af 100644 --- a/vyper/semantics/types/user/struct.py +++ b/vyper/semantics/types/user/struct.py @@ -21,11 +21,11 @@ def __init__( _id: str, members: dict, location: DataLocation = DataLocation.MEMORY, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: self._id = _id - super().__init__(location, is_immutable, is_public) + super().__init__(location, is_constant, is_public) for key, type_ in members.items(): self.add_member(key, type_) @@ -66,12 +66,12 @@ def from_annotation( self, node: vy_ast.VyperNode, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> StructDefinition: if not isinstance(node, vy_ast.Name): raise StructureException("Invalid type assignment", node) - return StructDefinition(self._id, self.members, location, is_immutable, is_public) + return StructDefinition(self._id, self.members, location, is_constant, is_public) def fetch_call_return(self, node: vy_ast.Call) -> StructDefinition: validate_call_args(node, 1) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index e45e0a5ac3..4186f1ccdc 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -69,7 +69,7 @@ def __ge__(self, other: object) -> bool: def get_type_from_abi( abi_type: Dict, location: DataLocation = DataLocation.UNSET, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> BaseTypeDefinition: """ @@ -101,7 +101,7 @@ def get_type_from_abi( raise UnknownType(f"ABI type has an invalid length: {type_string}") from None try: value_type = get_type_from_abi( - {"type": value_type_string}, location=location, is_immutable=is_immutable + {"type": value_type_string}, location=location, is_constant=is_constant ) except UnknownType: raise UnknownType(f"ABI contains unknown type: {type_string}") from None @@ -110,7 +110,7 @@ def get_type_from_abi( value_type, length, location=location, - is_immutable=is_immutable, + is_constant=is_constant, is_public=is_public, ) except InvalidType: @@ -119,7 +119,7 @@ def get_type_from_abi( else: try: return namespace[type_string]._type( - location=location, is_immutable=is_immutable, is_public=is_public + location=location, is_constant=is_constant, is_public=is_public ) except KeyError: raise UnknownType(f"ABI contains unknown type: {type_string}") from None @@ -128,7 +128,7 @@ def get_type_from_abi( def get_type_from_annotation( node: vy_ast.VyperNode, location: DataLocation, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> BaseTypeDefinition: """ @@ -158,11 +158,11 @@ def get_type_from_annotation( if getattr(type_obj, "_as_array", False) and isinstance(node, vy_ast.Subscript): # if type can be an array and node is a subscript, create an `ArrayDefinition` length = get_index_value(node.slice) - value_type = get_type_from_annotation(node.value, location, is_immutable, False) - return ArrayDefinition(value_type, length, location, is_immutable, is_public) + value_type = get_type_from_annotation(node.value, location, is_constant, False) + return ArrayDefinition(value_type, length, location, is_constant, is_public) try: - return type_obj.from_annotation(node, location, is_immutable, is_public) + return type_obj.from_annotation(node, location, is_constant, is_public) except AttributeError: raise InvalidType(f"'{type_name}' is not a valid type", node) from None @@ -195,4 +195,4 @@ def check_constant(node: vy_ast.VyperNode) -> bool: return True value_type = get_exact_type_from_node(node) - return getattr(value_type, "is_immutable", False) + return getattr(value_type, "is_constant", False) diff --git a/vyper/semantics/types/value/address.py b/vyper/semantics/types/value/address.py index 1bb9906942..96bf66dd43 100644 --- a/vyper/semantics/types/value/address.py +++ b/vyper/semantics/types/value/address.py @@ -11,10 +11,10 @@ class AddressDefinition(MemberTypeDefinition): _id = "address" _type_members = { - "balance": Uint256Definition(is_immutable=True), - "codehash": Bytes32Definition(is_immutable=True), - "codesize": Uint256Definition(is_immutable=True), - "is_contract": BoolDefinition(is_immutable=True), + "balance": Uint256Definition(is_constant=True), + "codehash": Bytes32Definition(is_constant=True), + "codesize": Uint256Definition(is_constant=True), + "is_contract": BoolDefinition(is_constant=True), } diff --git a/vyper/semantics/types/value/array_value.py b/vyper/semantics/types/value/array_value.py index 56575daf4b..f994bf5a6e 100644 --- a/vyper/semantics/types/value/array_value.py +++ b/vyper/semantics/types/value/array_value.py @@ -38,10 +38,10 @@ def __init__( self, length: int = 0, location: DataLocation = DataLocation.MEMORY, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> None: - super().__init__(location, is_immutable, is_public) + super().__init__(location, is_constant, is_public) self._length = length self._min_length = length @@ -120,7 +120,7 @@ def from_annotation( cls, node: vy_ast.VyperNode, location: DataLocation = DataLocation.MEMORY, - is_immutable: bool = False, + is_constant: bool = False, is_public: bool = False, ) -> _ArrayValueDefinition: if not isinstance(node, vy_ast.Subscript): @@ -133,7 +133,7 @@ def from_annotation( raise UnexpectedValue("Node id does not match type name") length = validation.utils.get_index_value(node.slice) # type: ignore - return cls._type(length, location, is_immutable, is_public) + return cls._type(length, location, is_constant, is_public) @classmethod def from_literal(cls, node: vy_ast.Constant) -> _ArrayValueDefinition: diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py index 7dbe88a718..f6476d3035 100644 --- a/vyper/semantics/validation/local.py +++ b/vyper/semantics/validation/local.py @@ -382,7 +382,7 @@ def visit_For(self, node): for type_ in type_list: # type check the for loop body using each possible type for iterator value type_ = copy.deepcopy(type_) - type_.is_immutable = True + type_.is_constant = True with self.namespace.enter_scope(): try: diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index f17e0dbf27..9926f4ec25 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -148,7 +148,7 @@ def visit_AnnAssign(self, node): self.namespace[interface_name].validate_implements(node) return - is_immutable, is_public = False, False + is_constant, is_public = False, False annotation = node.annotation if isinstance(annotation, vy_ast.Call): # the annotation is a function call, e.g. `foo: constant(uint256)` @@ -157,7 +157,7 @@ def visit_AnnAssign(self, node): validate_call_args(annotation, 1) if call_name == "constant": # declaring a constant - is_immutable = True + is_constant = True elif call_name == "public": # declaring a public variable @@ -171,11 +171,11 @@ def visit_AnnAssign(self, node): annotation = annotation.args[0] type_definition = get_type_from_annotation( - annotation, DataLocation.STORAGE, is_immutable, is_public + annotation, DataLocation.STORAGE, is_constant, is_public ) node._metadata["type"] = type_definition - if is_immutable: + if is_constant: if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) if not check_literal(node.value): From b06886ed58609adced64cc4682b5eb7b09e77298 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 11:10:19 -0400 Subject: [PATCH 02/56] feat: add `immutable` as a reserved keyword --- vyper/semantics/namespace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/semantics/namespace.py b/vyper/semantics/namespace.py index 79a5c8ad8e..09b9d40412 100644 --- a/vyper/semantics/namespace.py +++ b/vyper/semantics/namespace.py @@ -117,6 +117,7 @@ def validate_identifier(attr): "external", "nonpayable", "constant", + "immutable", "internal", "payable", "nonreentrant", From d967cf2aafffeba5ef316d3af3f37b8e4b35e239 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 11:14:52 -0400 Subject: [PATCH 03/56] feat: handle immutable defs in ModuleNodeVisitor --- vyper/semantics/validation/module.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index 9926f4ec25..bc4e800951 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -148,12 +148,12 @@ def visit_AnnAssign(self, node): self.namespace[interface_name].validate_implements(node) return - is_constant, is_public = False, False + is_constant, is_public, is_immutable = False, False, False annotation = node.annotation if isinstance(annotation, vy_ast.Call): # the annotation is a function call, e.g. `foo: constant(uint256)` call_name = annotation.get("func.id") - if call_name in ("constant", "public"): + if call_name in ("constant", "public", "immutable"): validate_call_args(annotation, 1) if call_name == "constant": # declaring a constant @@ -167,11 +167,16 @@ def visit_AnnAssign(self, node): # we need this when builing the public getter node._metadata["func_type"] = ContractFunction.from_AnnAssign(node) + elif call_name == "immutable": + # declaring an immutable variable + is_immutable = True + # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] + data_loc = DataLocation.UNSET if is_immutable else DataLocation.STORAGE type_definition = get_type_from_annotation( - annotation, DataLocation.STORAGE, is_constant, is_public + annotation, data_loc, is_constant, is_public, is_immutable ) node._metadata["type"] = type_definition @@ -189,10 +194,18 @@ def visit_AnnAssign(self, node): return if node.value: + var_type = "Immutable" if is_immutable else "Storage" raise VariableDeclarationException( - "Storage variables cannot have an initial value", node.value + f"{var_type} variables cannot have an initial value", node.value ) + if is_immutable: + try: + self.namespace[name] = type_definition + except VyperException as exc: + raise exc.with_annotation(node) from None + return + try: self.namespace.validate_assignment(name) except NamespaceCollision as exc: From 9a407b657afb7db750116e55b8c0e3d2aae6bd06 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 11:26:40 -0400 Subject: [PATCH 04/56] fix: add `is_immutable` kwarg to get_type_from_annotation --- vyper/semantics/types/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 4186f1ccdc..599966d72d 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -130,6 +130,7 @@ def get_type_from_annotation( location: DataLocation, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> BaseTypeDefinition: """ Return a type object for the given AST node. @@ -156,13 +157,14 @@ def get_type_from_annotation( raise UnknownType(f"No builtin or user-defined type named '{type_name}'", node) from None if getattr(type_obj, "_as_array", False) and isinstance(node, vy_ast.Subscript): + # TODO: handle `is_immutable` for arrays # if type can be an array and node is a subscript, create an `ArrayDefinition` length = get_index_value(node.slice) value_type = get_type_from_annotation(node.value, location, is_constant, False) return ArrayDefinition(value_type, length, location, is_constant, is_public) try: - return type_obj.from_annotation(node, location, is_constant, is_public) + return type_obj.from_annotation(node, location, is_constant, is_public, is_immutable) except AttributeError: raise InvalidType(f"'{type_name}' is not a valid type", node) from None From ba277265173948720b332cf64f4afe2cca6c28c1 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 11:34:53 -0400 Subject: [PATCH 05/56] feat: add `is_immutable` keyword to primitive classes --- vyper/semantics/types/bases.py | 5 ++++- vyper/semantics/types/indexable/mapping.py | 3 ++- vyper/semantics/types/value/array_value.py | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index bfc6d42172..e2459df86b 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -100,6 +100,7 @@ def from_annotation( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> "BaseTypeDefinition": """ Generate a `BaseTypeDefinition` instance of this type from `AnnAssign.annotation` @@ -118,7 +119,7 @@ def from_annotation( raise StructureException("Invalid type assignment", node) if node.id != cls._id: raise UnexpectedValue("Node id does not match type name") - return cls._type(location, is_constant, is_public) + return cls._type(location, is_constant, is_public, is_immutable) @classmethod def from_literal(cls, node: vy_ast.Constant) -> "BaseTypeDefinition": @@ -242,10 +243,12 @@ def __init__( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: self.location = location self.is_constant = is_constant self.is_public = is_public + self.is_immutable = is_immutable @property def canonical_type(self) -> str: diff --git a/vyper/semantics/types/indexable/mapping.py b/vyper/semantics/types/indexable/mapping.py index 4935e07fb0..36477bab6e 100644 --- a/vyper/semantics/types/indexable/mapping.py +++ b/vyper/semantics/types/indexable/mapping.py @@ -36,6 +36,7 @@ def from_annotation( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> MappingDefinition: if ( not isinstance(node, vy_ast.Subscript) @@ -46,7 +47,7 @@ def from_annotation( raise StructureException( "HashMap must be defined with a key type and a value type", node ) - if location != DataLocation.STORAGE: + if location != DataLocation.STORAGE or is_immutable: raise StructureException("HashMap can only be declared as a storage variable", node) key_type = get_type_from_annotation(node.slice.value.elements[0], DataLocation.UNSET) diff --git a/vyper/semantics/types/value/array_value.py b/vyper/semantics/types/value/array_value.py index f994bf5a6e..82896c5c3d 100644 --- a/vyper/semantics/types/value/array_value.py +++ b/vyper/semantics/types/value/array_value.py @@ -40,8 +40,9 @@ def __init__( location: DataLocation = DataLocation.MEMORY, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: - super().__init__(location, is_constant, is_public) + super().__init__(location, is_constant, is_public, is_immutable) self._length = length self._min_length = length @@ -122,6 +123,7 @@ def from_annotation( location: DataLocation = DataLocation.MEMORY, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> _ArrayValueDefinition: if not isinstance(node, vy_ast.Subscript): raise StructureException( @@ -133,7 +135,7 @@ def from_annotation( raise UnexpectedValue("Node id does not match type name") length = validation.utils.get_index_value(node.slice) # type: ignore - return cls._type(length, location, is_constant, is_public) + return cls._type(length, location, is_constant, is_public, is_immutable) @classmethod def from_literal(cls, node: vy_ast.Constant) -> _ArrayValueDefinition: From a246ca480493f35a51270748d6272e1c85666081 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 12:53:08 -0400 Subject: [PATCH 06/56] feat: set positions of immutable vars --- vyper/semantics/types/bases.py | 11 +++++++++++ vyper/semantics/validation/data_positions.py | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index e2459df86b..e94c591f30 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -69,6 +69,17 @@ def __repr__(self): return f"" +class ImmutableSlot(DataPosition): + __slots__ = ("position",) + _location = DataLocation.UNSET + + def __init__(self, position): + self.position = position + + def __repr__(self): + return f"" + + class BasePrimitive: """ Base class for primitive type classes. diff --git a/vyper/semantics/validation/data_positions.py b/vyper/semantics/validation/data_positions.py index 74bbf7261e..f46574d42e 100644 --- a/vyper/semantics/validation/data_positions.py +++ b/vyper/semantics/validation/data_positions.py @@ -3,7 +3,7 @@ from typing import Dict from vyper import ast as vy_ast -from vyper.semantics.types.bases import StorageSlot +from vyper.semantics.types.bases import ImmutableSlot, StorageSlot from vyper.typing import StorageLayout @@ -17,6 +17,7 @@ def set_data_positions(vyper_module: vy_ast.Module) -> StorageLayout: vyper_module : vy_ast.Module Top-level Vyper AST node that has already been annotated with type data. """ + set_immutable_slots(vyper_module) return set_storage_slots(vyper_module) @@ -62,6 +63,10 @@ def set_storage_slots(vyper_module: vy_ast.Module) -> StorageLayout: storage_slot += 1 for node in vyper_module.get_children(vy_ast.AnnAssign): + + if node.get("annotation.func.id") == "immutable": + continue + type_ = node.target._metadata["type"] type_.set_position(StorageSlot(storage_slot)) @@ -84,3 +89,15 @@ def set_calldata_offsets(fn_node: vy_ast.FunctionDef) -> None: def set_memory_offsets(fn_node: vy_ast.FunctionDef) -> None: pass + + +def set_immutable_slots(vyper_module: vy_ast.Module) -> None: + + slot = 0 + for node in vyper_module.get_children( + vy_ast.AnnAssign, filters={"annotation.func.id": "immutable"} + ): + type_ = node._metadata["type"] + type_.set_position(ImmutableSlot(slot)) + + slot += math.ceil(type_.size_in_bytes / 32) From 62d06e342f8537391f03e95e7f76a0215ff8eebc Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 12:53:35 -0400 Subject: [PATCH 07/56] fix: parse_type of immutable var --- vyper/old_codegen/types/types.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/old_codegen/types/types.py b/vyper/old_codegen/types/types.py index bbd0547e56..d00963419b 100644 --- a/vyper/old_codegen/types/types.py +++ b/vyper/old_codegen/types/types.py @@ -232,6 +232,9 @@ def parse_type(item, location=None, sigs=None, custom_structs=None): custom_structs[item.id], custom_structs, ) + if item.func.id == "immutable": + return BaseType(item.args[0].id) + raise InvalidType("Units are no longer supported", item) # Subscripts elif isinstance(item, vy_ast.Subscript): From 859c935de401594bff2c994ca41b0bd510d9b57e Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 13:08:01 -0400 Subject: [PATCH 08/56] fix: prevent immutable modification outside constructor --- vyper/semantics/types/bases.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index e94c591f30..f6cd042ff8 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -442,6 +442,8 @@ def validate_modification(self, node: Union[vy_ast.Assign, vy_ast.AugAssign]) -> raise ImmutableViolation("Cannot write to calldata", node) if self.is_constant: raise ImmutableViolation("Immutable value cannot be written to", node) + if self.is_immutable and node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": + raise ImmutableViolation("Immutable value cannot be written to", node) if isinstance(node, vy_ast.AugAssign): self.validate_numeric_op(node) From 2f0e78b771acf519e99b9790501bd9acf48ff2e7 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 30 Sep 2021 16:06:19 -0400 Subject: [PATCH 09/56] WIP: add branch for immutable vars in expr.py --- vyper/old_codegen/expr.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 7b76f8c972..6e967dbbbf 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -316,6 +316,32 @@ def parse_Name(self): return LLLnode.from_list( [obj], typ=BaseType(typ, is_literal=True), pos=getpos(self.expr) ) + elif self.expr._metadata["type"].is_immutable: + # immutable variable + # need to handle constructor and outside constructor + var = self.context.globals[self.expr.id] + is_constructor = self.expr.get_ancestor(vy_ast.FunctionDef).get("name") == "__init__" + if is_constructor: + pos = self.context.new_internal_variable(var.typ) + var.pos = pos + return LLLnode.from_list( + pos, + typ=var.typ, + location="memory", + pos=getpos(self.expr), + annotation=self.expr.id, + mutable=True, + ) + else: + pos = self.context.new_internal_variable(var.typ) + return LLLnode.from_list( + ["seq", ["codecopy", pos, "codesize", 32], pos], + typ=var.typ, + location="memory", + pos=getpos(self.expr), + annotation=self.expr.id, + mutable=False, + ) # x.y or x[5] def parse_Attribute(self): From e98052363a465fc273fd798fe1254516ce1bfc3f Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sat, 2 Oct 2021 17:55:41 -0400 Subject: [PATCH 10/56] fix: DataPosition class ImmutableSlot -> CodeOffset --- vyper/semantics/types/bases.py | 13 +++++++------ vyper/semantics/validation/data_positions.py | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index f6cd042ff8..aaf8c981e8 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -23,6 +23,7 @@ class DataLocation(Enum): MEMORY = 1 STORAGE = 2 CALLDATA = 3 + CODE = 4 class DataPosition: @@ -69,15 +70,15 @@ def __repr__(self): return f"" -class ImmutableSlot(DataPosition): - __slots__ = ("position",) - _location = DataLocation.UNSET +class CodeOffset(DataPosition): + __slots__ = ("offset",) + _location = DataLocation.CODE - def __init__(self, position): - self.position = position + def __init__(self, offset): + self.offset = offset def __repr__(self): - return f"" + return f"" class BasePrimitive: diff --git a/vyper/semantics/validation/data_positions.py b/vyper/semantics/validation/data_positions.py index f46574d42e..e75d279cd7 100644 --- a/vyper/semantics/validation/data_positions.py +++ b/vyper/semantics/validation/data_positions.py @@ -3,7 +3,7 @@ from typing import Dict from vyper import ast as vy_ast -from vyper.semantics.types.bases import ImmutableSlot, StorageSlot +from vyper.semantics.types.bases import CodeOffset, StorageSlot from vyper.typing import StorageLayout @@ -17,7 +17,7 @@ def set_data_positions(vyper_module: vy_ast.Module) -> StorageLayout: vyper_module : vy_ast.Module Top-level Vyper AST node that has already been annotated with type data. """ - set_immutable_slots(vyper_module) + set_code_offsets(vyper_module) return set_storage_slots(vyper_module) @@ -91,13 +91,13 @@ def set_memory_offsets(fn_node: vy_ast.FunctionDef) -> None: pass -def set_immutable_slots(vyper_module: vy_ast.Module) -> None: +def set_code_offsets(vyper_module: vy_ast.Module) -> None: - slot = 0 + offset = 0 for node in vyper_module.get_children( vy_ast.AnnAssign, filters={"annotation.func.id": "immutable"} ): type_ = node._metadata["type"] - type_.set_position(ImmutableSlot(slot)) + type_.set_position(CodeOffset(offset)) - slot += math.ceil(type_.size_in_bytes / 32) + offset += math.ceil(type_.size_in_bytes / 32) * 32 From 9645b1b1a8b848df00c889678939223784d78532 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sat, 2 Oct 2021 18:34:15 -0400 Subject: [PATCH 11/56] fix: runtime loading of immutables --- vyper/old_codegen/expr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 6e967dbbbf..88852883a7 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -333,11 +333,11 @@ def parse_Name(self): mutable=True, ) else: - pos = self.context.new_internal_variable(var.typ) + pos = self.expr._metadata["type"].position return LLLnode.from_list( - ["seq", ["codecopy", pos, "codesize", 32], pos], + ["add", "codesize", pos.offset], typ=var.typ, - location="memory", + location="code", pos=getpos(self.expr), annotation=self.expr.id, mutable=False, From 66c225cd2954467259a39f5dc6018b683c1e869f Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sat, 2 Oct 2021 20:11:24 -0400 Subject: [PATCH 12/56] feat: append to init lll storage of immutables --- .../function_definitions/external_function.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vyper/old_codegen/function_definitions/external_function.py b/vyper/old_codegen/function_definitions/external_function.py index 8ebfd1b4c2..7bed16a933 100644 --- a/vyper/old_codegen/function_definitions/external_function.py +++ b/vyper/old_codegen/function_definitions/external_function.py @@ -1,6 +1,7 @@ from typing import Any, List import vyper.utils as util +from vyper import ast as vy_ast from vyper.ast.signatures.function_signature import FunctionSignature, VariableRecord from vyper.exceptions import CompilerPanic from vyper.old_codegen.context import Context @@ -186,6 +187,19 @@ def generate_lll_for_external_function(code, sig, context, check_nonpayable): exit = [["label", sig.exit_sequence_label]] + nonreentrant_post if sig.is_init_func: + done = set() + for assign in code.get_children(vy_ast.Assign): + typ = assign.target._metadata["type"] + if not typ.is_immutable or assign.target.id in done: + continue + exit.append( + [ + "mstore", + ["add", "~codelen", typ.position.offset], + ["mload", context.globals[assign.target.id].pos], + ] + ) + pass # init func has special exit sequence generated by parser.py elif context.return_type is None: exit += [["stop"]] From a975a6ca68bfe317569df1851dfccc0aa56bb0bf Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sat, 2 Oct 2021 20:47:08 -0400 Subject: [PATCH 13/56] fix: update VariableRecord class + add immutables to global ctx --- vyper/ast/signatures/function_signature.py | 2 ++ vyper/old_codegen/global_context.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/vyper/ast/signatures/function_signature.py b/vyper/ast/signatures/function_signature.py index d7cf1bebe5..2dc6a9d2a1 100644 --- a/vyper/ast/signatures/function_signature.py +++ b/vyper/ast/signatures/function_signature.py @@ -23,6 +23,7 @@ def __init__( blockscopes=None, defined_at=None, is_internal=False, + is_immutable=False, ): self.name = name self.pos = pos @@ -33,6 +34,7 @@ def __init__( self.blockscopes = [] if blockscopes is None else blockscopes self.defined_at = defined_at # source code location variable record was defined. self.is_internal = is_internal + self.is_immutable = is_immutable def __repr__(self): ret = vars(self) diff --git a/vyper/old_codegen/global_context.py b/vyper/old_codegen/global_context.py index 3ca36952a9..a9a13acce3 100644 --- a/vyper/old_codegen/global_context.py +++ b/vyper/old_codegen/global_context.py @@ -208,6 +208,15 @@ def add_globals_and_events(self, item): typ, True, ) + elif self.get_call_func_name(item) == "immutable": + typ = self.parse_type(item.annotation.args[0], "code") + self._globals[item.target.id] = VariableRecord( + item.target.id, + len(self._globals), + typ, + False, + is_immutable=True, + ) elif isinstance(item.annotation, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): self._globals[item.target.id] = VariableRecord( From 538d31e601a0e1dead9cdfbbcaae4ba4dcef4209 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sat, 2 Oct 2021 20:52:29 -0400 Subject: [PATCH 14/56] fix: modify constructor to return runtime + immutables --- vyper/old_codegen/parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index b4ad4f9c60..dc63608e41 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -177,8 +177,12 @@ def parse_regular_functions( ] runtime.extend(internal_funcs) + immutables_len = sum( + [glob.size * 32 for glob in global_ctx._globals.values() if glob.is_immutable] + ) + # TODO CMC 20210911 why does the lll have a trailing 0 - o.append(["return", 0, ["lll", runtime, 0]]) + o.append(["return", 0, ["add", ["lll", runtime, 0], immutables_len]]) return o, runtime From da098cd3fd9a72b47d467d3b83c35673af485618 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 00:18:16 -0400 Subject: [PATCH 15/56] fix: constructor handles data section --- vyper/old_codegen/parser.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index dc63608e41..9d7fd7ed66 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -177,12 +177,39 @@ def parse_regular_functions( ] runtime.extend(internal_funcs) - immutables_len = sum( - [glob.size * 32 for glob in global_ctx._globals.values() if glob.is_immutable] - ) + immutables = [_global for _global in global_ctx._globals.values() if _global.is_immutable] + + if immutables: + # find position of the last immutable so we do not overwrite it in memory + # when we codecopy the runtime code to memory + immutables = sorted(immutables, key=lambda imm: imm.pos) + start_pos = immutables[-1].pos + immutables[-1].size * 32 + # create sequence of actions to copy immutables to the end of the runtime code in memory + data_section = [] + offset = 0 + for immutable in immutables: + # store each immutable at the end of the runtime code + data_section.append( + ["mstore", ["add", start_pos + offset, "_lllsz"], ["mload", immutable.pos]] + ) + offset += immutable.size * 32 + + o.append( + [ + "with", + "_lllsz", # keep size of runtime bytecode in sz var + ["lll", runtime, start_pos], # store runtime code at `start_pos` + # sequence of copying immutables, with final action of returning the runtime code + ["seq", *data_section, ["return", start_pos, ["add", offset, "_lllsz"]]], + ] + ) + + else: + # NOTE: lll macro trailing 0 is the location in memory to store + # the compiled bytecode + # https://lll-docs.readthedocs.io/en/latest/lll_reference.html#code-lll + o.append(["return", 0, ["lll", runtime, 0]]) - # TODO CMC 20210911 why does the lll have a trailing 0 - o.append(["return", 0, ["add", ["lll", runtime, 0], immutables_len]]) return o, runtime From 12fe4d16453e359e2c54a0e2ed08ec4c7f01e721 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 00:47:55 -0400 Subject: [PATCH 16/56] fix: account for immutables at end of runtime code Immutable values are appended to the runtime code, and so using `codesize` opcode will include them in the returned size. However, the immutables section of the runtime code is not at the end, so to access it we need to account for the fact that `codesize` gives us an inflated value. We do this by taking the sum of all the allocated space of all immutables, and subtrace codesize by this and then add the offset of the data. --- vyper/old_codegen/expr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 88852883a7..073a6fecfd 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -333,9 +333,12 @@ def parse_Name(self): mutable=True, ) else: + immutable_section_size = sum( + [imm.size * 32 for imm in self.context.globals.values() if imm.is_immutable] + ) pos = self.expr._metadata["type"].position return LLLnode.from_list( - ["add", "codesize", pos.offset], + ["sub", "codesize", immutable_section_size - pos.offset], typ=var.typ, location="code", pos=getpos(self.expr), From 5b50471f4550fb70157112039ede919ca2bedf74 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 00:52:01 -0400 Subject: [PATCH 17/56] fix: vyper grammar include immutable_def --- tests/grammar/vyper.lark | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/grammar/vyper.lark b/tests/grammar/vyper.lark index 2b9783ac9e..5533cab2c5 100644 --- a/tests/grammar/vyper.lark +++ b/tests/grammar/vyper.lark @@ -12,6 +12,7 @@ module: ( DOCSTRING | variable_def | event_def | function_def + | immutable_def | _NEWLINE )* @@ -36,6 +37,10 @@ import: _IMPORT DOT* _import_path [import_alias] // NOTE: Temporary until decorators used constant_def: NAME ":" "constant" "(" type ")" "=" _expr +// immutable definitions +// NOTE: Temporary until decorators used +immutable_def: NAME ":" "immutable" "(" type ")" + variable: NAME ":" type // NOTE: Temporary until decorators used variable_with_getter: NAME ":" "public" "(" type ")" From ac5517329bb8caa11813bb567884527d1cac9376 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 00:54:38 -0400 Subject: [PATCH 18/56] test: simple usage of immutable keyword with uint256 --- tests/parser/features/test_immutable.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/parser/features/test_immutable.py diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py new file mode 100644 index 0000000000..f7d2d6d76c --- /dev/null +++ b/tests/parser/features/test_immutable.py @@ -0,0 +1,15 @@ +def test_simple_usage(get_contract): + code = """ +VALUE: immutable(uint256) + +@external +def __init__(_value: uint256): + VALUE = _value + +@view +@external +def get_value() -> uint256: + return VALUE +""" + c = get_contract(code, 42) + assert c.get_value() == 42 From b9542dce03603d933c95fb7e8410eed15110bb93 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 01:01:45 -0400 Subject: [PATCH 19/56] fix: remove copying of immutables from external_function.py --- .../function_definitions/external_function.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/vyper/old_codegen/function_definitions/external_function.py b/vyper/old_codegen/function_definitions/external_function.py index 7bed16a933..8ebfd1b4c2 100644 --- a/vyper/old_codegen/function_definitions/external_function.py +++ b/vyper/old_codegen/function_definitions/external_function.py @@ -1,7 +1,6 @@ from typing import Any, List import vyper.utils as util -from vyper import ast as vy_ast from vyper.ast.signatures.function_signature import FunctionSignature, VariableRecord from vyper.exceptions import CompilerPanic from vyper.old_codegen.context import Context @@ -187,19 +186,6 @@ def generate_lll_for_external_function(code, sig, context, check_nonpayable): exit = [["label", sig.exit_sequence_label]] + nonreentrant_post if sig.is_init_func: - done = set() - for assign in code.get_children(vy_ast.Assign): - typ = assign.target._metadata["type"] - if not typ.is_immutable or assign.target.id in done: - continue - exit.append( - [ - "mstore", - ["add", "~codelen", typ.position.offset], - ["mload", context.globals[assign.target.id].pos], - ] - ) - pass # init func has special exit sequence generated by parser.py elif context.return_type is None: exit += [["stop"]] From 0f0bc1b0ced976e3bbfb0202a63ff4e7170526da Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 01:14:41 -0400 Subject: [PATCH 20/56] fix: add `is_immutable` kwarg to more Definition classes --- vyper/semantics/types/bases.py | 3 ++- vyper/semantics/types/user/interface.py | 8 ++++++-- vyper/semantics/types/user/struct.py | 8 ++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index aaf8c981e8..5692cae133 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -517,8 +517,9 @@ def __init__( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: - super().__init__(location, is_constant, is_public) + super().__init__(location, is_constant, is_public, is_immutable) self.members: OrderedDict = OrderedDict() def add_member(self, name: str, type_: BaseTypeDefinition) -> None: diff --git a/vyper/semantics/types/user/interface.py b/vyper/semantics/types/user/interface.py index 508aaa4b44..7a131b303d 100644 --- a/vyper/semantics/types/user/interface.py +++ b/vyper/semantics/types/user/interface.py @@ -24,9 +24,10 @@ def __init__( location: DataLocation = DataLocation.MEMORY, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: self._id = _id - super().__init__(location, is_constant, is_public) + super().__init__(location, is_constant, is_public, is_immutable) for key, type_ in members.items(): self.add_member(key, type_) @@ -56,12 +57,15 @@ def from_annotation( location: DataLocation = DataLocation.MEMORY, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> InterfaceDefinition: if not isinstance(node, vy_ast.Name): raise StructureException("Invalid type assignment", node) - return InterfaceDefinition(self._id, self.members, location, is_constant, is_public) + return InterfaceDefinition( + self._id, self.members, location, is_constant, is_public, is_immutable + ) def fetch_call_return(self, node: vy_ast.Call) -> InterfaceDefinition: validate_call_args(node, 1) diff --git a/vyper/semantics/types/user/struct.py b/vyper/semantics/types/user/struct.py index 7f80c896af..4b28c07915 100644 --- a/vyper/semantics/types/user/struct.py +++ b/vyper/semantics/types/user/struct.py @@ -23,9 +23,10 @@ def __init__( location: DataLocation = DataLocation.MEMORY, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: self._id = _id - super().__init__(location, is_constant, is_public) + super().__init__(location, is_constant, is_public, is_immutable) for key, type_ in members.items(): self.add_member(key, type_) @@ -68,10 +69,13 @@ def from_annotation( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> StructDefinition: if not isinstance(node, vy_ast.Name): raise StructureException("Invalid type assignment", node) - return StructDefinition(self._id, self.members, location, is_constant, is_public) + return StructDefinition( + self._id, self.members, location, is_constant, is_public, is_immutable + ) def fetch_call_return(self, node: vy_ast.Call) -> StructDefinition: validate_call_args(node, 1) From 7dbe7c3f50d638a4162342101b8464feea58fc5f Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 14:15:03 -0400 Subject: [PATCH 21/56] fix: raise syntax exception if immutable not assigned a value --- vyper/semantics/validation/module.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index bc4e800951..009033ed50 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -14,6 +14,7 @@ NamespaceCollision, StateAccessViolation, StructureException, + SyntaxException, UndeclaredDefinition, VariableDeclarationException, VyperException, @@ -171,6 +172,20 @@ def visit_AnnAssign(self, node): # declaring an immutable variable is_immutable = True + # mutability is checked automatically preventing assignment + # outside of the constructor, here we just check a value is assigned, + # not necessarily where + assignments = self.ast.get_descendants( + vy_ast.Assign, filters={"target.id": node.target.id} + ) + if not assignments: + raise SyntaxException( + "Immutable definition requires an assignment in the constructor", + node.node_source_code, + node.lineno, + node.col_offset, + ) + # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] From e63cdf1d8565e4559df85919a17e10b44c67efef Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 14:35:48 -0400 Subject: [PATCH 22/56] test: immutable syntax, simple cases --- tests/parser/syntax/test_immutables.py | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/parser/syntax/test_immutables.py diff --git a/tests/parser/syntax/test_immutables.py b/tests/parser/syntax/test_immutables.py new file mode 100644 index 0000000000..02e6852fa9 --- /dev/null +++ b/tests/parser/syntax/test_immutables.py @@ -0,0 +1,93 @@ +import pytest + +from vyper import compile_code + +fail_list = [ + # VALUE is not set in the constructor + """ +VALUE: immutable(uint256) + +@external +def __init__(): + pass + """, + # no `__init__` function, VALUE not set + """ +VALUE: immutable(uint256) + +@view +@external +def get_value() -> uint256: + return VALUE + """, + # VALUE given an initial value + """ +VALUE: immutable(uint256) = 3 + +@external +def __init__(): + pass + """, + # setting value outside of constructor + """ +VALUE: immutable(uint256) + +@external +def __init__(): + VALUE = 0 + +@external +def set_value(_value: uint256): + VALUE = _value + """, +] + + +@pytest.mark.parametrize("bad_code", fail_list) +def test_compilation_fails_with_exception(bad_code): + with pytest.raises(Exception): + compile_code(bad_code) + + +pass_list = [ + f""" +VALUE: immutable({typ}) + +@external +def __init__(_value: {typ}): + VALUE = _value + +@view +@external +def get_value() -> {typ}: + return VALUE + """ + for typ in ( + "uint256", + "int256", + "int128", + "address", + "Bytes[64]", + "bytes32", + "decimal", + "bool", + "String[10]", + ) +] + +pass_list += [ + # using immutable allowed in constructor + """ +VALUE: immutable(uint256) + +@external +def __init__(_value: uint256): + VALUE = _value * 3 + VALUE = VALUE + 1 + """ +] + + +@pytest.mark.parametrize("good_code", pass_list) +def test_compilation_passes(good_code): + assert compile_code(good_code) From cd2cb985b6c9332ff291d62eb11aaff74b374d10 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 15:26:58 -0400 Subject: [PATCH 23/56] test: accessing stored immutable --- tests/parser/features/test_immutable.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index f7d2d6d76c..0dc7943151 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -1,3 +1,38 @@ +import pytest + +code_list = [ + ( + f""" +VALUE: immutable({typ}) + +@external +def __init__(_value: {typ}): + VALUE = _value + +@view +@external +def get_value() -> {typ}: + return VALUE + """, + value, + ) + for typ, value in ( + ("uint256", 42), + ("int256", -(2 ** 200)), + ("int128", -(2 ** 126)), + ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + ("bytes32", b"deadbeef" * 4), + ("bool", True), + ) +] + + +@pytest.mark.parametrize("code,value", code_list) +def test_value_is_stored_correctly(code, value, get_contract): + c = get_contract(code, value) + assert c.get_value() == value + + def test_simple_usage(get_contract): code = """ VALUE: immutable(uint256) From c0eeeaf07cd62c1b5b45606fc89d77ab2d9b705f Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 15:34:44 -0400 Subject: [PATCH 24/56] test: verify immutables of dynamic length are disallowed --- tests/parser/syntax/test_immutables.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/parser/syntax/test_immutables.py b/tests/parser/syntax/test_immutables.py index 02e6852fa9..7b8d739e7c 100644 --- a/tests/parser/syntax/test_immutables.py +++ b/tests/parser/syntax/test_immutables.py @@ -42,6 +42,25 @@ def set_value(_value: uint256): """, ] +fail_list += [ + f""" +VALUE: immutable({typ}) + +@external +def __init__(_value: {typ}): + VALUE = _value + +@view +@external +def get_value() -> {typ}: + return VALUE + """ + for typ in ( + "Bytes[64]", + "String[10]", + ) +] + @pytest.mark.parametrize("bad_code", fail_list) def test_compilation_fails_with_exception(bad_code): @@ -67,11 +86,9 @@ def get_value() -> {typ}: "int256", "int128", "address", - "Bytes[64]", "bytes32", "decimal", "bool", - "String[10]", ) ] From 6a3c94642690d16bda7228a10876758965ea35b9 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 15:35:08 -0400 Subject: [PATCH 25/56] fix: disallow bytes/string immutables (momentarily) --- vyper/semantics/validation/module.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index 009033ed50..c1a0465826 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -215,6 +215,11 @@ def visit_AnnAssign(self, node): ) if is_immutable: + if type_definition.canonical_type in ("bytes", "string"): + raise VariableDeclarationException( + "Variable length immutable values are disallowed", annotation + ) + try: self.namespace[name] = type_definition except VyperException as exc: From b6c92799ffa552bd53cb0e8fb5d8df663a86f24b Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Sun, 3 Oct 2021 23:23:21 -0400 Subject: [PATCH 26/56] fix: use make_setter for memory cp operation of immutables --- vyper/old_codegen/parser.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index 9d7fd7ed66..00c1e82a31 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -14,6 +14,7 @@ ) from vyper.old_codegen.global_context import GlobalContext from vyper.old_codegen.lll_node import LLLnode +from vyper.old_codegen.parser_utils import make_setter from vyper.semantics.types.function import FunctionVisibility, StateMutability from vyper.typing import InterfaceImports from vyper.utils import LOADED_LIMITS @@ -189,9 +190,11 @@ def parse_regular_functions( offset = 0 for immutable in immutables: # store each immutable at the end of the runtime code - data_section.append( - ["mstore", ["add", start_pos + offset, "_lllsz"], ["mload", immutable.pos]] + lhs = LLLnode.from_list( + ["add", start_pos + offset, "_lllsz"], typ=immutable.typ, location="memory" ) + rhs = LLLnode.from_list(immutable.pos, typ=immutable.typ, location="memory") + data_section.append(make_setter(lhs, rhs, None)) offset += immutable.size * 32 o.append( From 02dd076eb7eece6a4d6f2910bbbe812a006fb6fb Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:29:36 -0400 Subject: [PATCH 27/56] fix: store memory loc + offset in data section in metadata --- vyper/old_codegen/expr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 073a6fecfd..77705d0c6e 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -322,10 +322,10 @@ def parse_Name(self): var = self.context.globals[self.expr.id] is_constructor = self.expr.get_ancestor(vy_ast.FunctionDef).get("name") == "__init__" if is_constructor: - pos = self.context.new_internal_variable(var.typ) - var.pos = pos + memory_loc = self.context.new_internal_variable(var.typ) + setattr(var, "_metadata", {"memory_loc": memory_loc, "data_offset": 0}) return LLLnode.from_list( - pos, + memory_loc, typ=var.typ, location="memory", pos=getpos(self.expr), @@ -336,9 +336,9 @@ def parse_Name(self): immutable_section_size = sum( [imm.size * 32 for imm in self.context.globals.values() if imm.is_immutable] ) - pos = self.expr._metadata["type"].position + offset = self.expr._metadata["type"].position.offset return LLLnode.from_list( - ["sub", "codesize", immutable_section_size - pos.offset], + ["sub", "codesize", immutable_section_size - offset], typ=var.typ, location="code", pos=getpos(self.expr), From 2118af0006ff467821ad6557536305cbf967134b Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:30:01 -0400 Subject: [PATCH 28/56] fix: use data offset + memory loc from metadata section --- vyper/old_codegen/parser.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index 00c1e82a31..c4ebd941ad 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -187,15 +187,17 @@ def parse_regular_functions( start_pos = immutables[-1].pos + immutables[-1].size * 32 # create sequence of actions to copy immutables to the end of the runtime code in memory data_section = [] - offset = 0 for immutable in immutables: # store each immutable at the end of the runtime code + memory_loc, offset = ( + immutable._metadata["memory_loc"], + immutable._metadata["data_offset"], + ) lhs = LLLnode.from_list( ["add", start_pos + offset, "_lllsz"], typ=immutable.typ, location="memory" ) - rhs = LLLnode.from_list(immutable.pos, typ=immutable.typ, location="memory") + rhs = LLLnode.from_list(memory_loc, typ=immutable.typ, location="memory") data_section.append(make_setter(lhs, rhs, None)) - offset += immutable.size * 32 o.append( [ From 9f144a0b839ed1f765cde06fb8908bca5fc2a48a Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:32:16 -0400 Subject: [PATCH 29/56] fix: remove restriction using strings/bytes immutables --- vyper/semantics/validation/module.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index c1a0465826..009033ed50 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -215,11 +215,6 @@ def visit_AnnAssign(self, node): ) if is_immutable: - if type_definition.canonical_type in ("bytes", "string"): - raise VariableDeclarationException( - "Variable length immutable values are disallowed", annotation - ) - try: self.namespace[name] = type_definition except VyperException as exc: From 11dddb43c95ed6ceaaf7cd4cdca21f98502a51b2 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:50:06 -0400 Subject: [PATCH 30/56] fix(test): verify usage of string/bytes immutables --- tests/parser/features/test_immutable.py | 2 ++ tests/parser/syntax/test_immutables.py | 21 ++------------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index 0dc7943151..bf32f2aa20 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -23,6 +23,8 @@ def get_value() -> {typ}: ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), ("bytes32", b"deadbeef" * 4), ("bool", True), + ("String[10]", "Vyper hiss"), + ("Bytes[10]", b"Vyper hiss"), ) ] diff --git a/tests/parser/syntax/test_immutables.py b/tests/parser/syntax/test_immutables.py index 7b8d739e7c..96a7064895 100644 --- a/tests/parser/syntax/test_immutables.py +++ b/tests/parser/syntax/test_immutables.py @@ -42,25 +42,6 @@ def set_value(_value: uint256): """, ] -fail_list += [ - f""" -VALUE: immutable({typ}) - -@external -def __init__(_value: {typ}): - VALUE = _value - -@view -@external -def get_value() -> {typ}: - return VALUE - """ - for typ in ( - "Bytes[64]", - "String[10]", - ) -] - @pytest.mark.parametrize("bad_code", fail_list) def test_compilation_fails_with_exception(bad_code): @@ -89,6 +70,8 @@ def get_value() -> {typ}: "bytes32", "decimal", "bool", + "Bytes[64]", + "String[10]", ) ] From 2e321191757babc0abb14235fd002cd8b08d4240 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:50:48 -0400 Subject: [PATCH 31/56] fix: only set _metadata on immutable during first pass --- vyper/old_codegen/expr.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 77705d0c6e..2176980855 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -322,8 +322,14 @@ def parse_Name(self): var = self.context.globals[self.expr.id] is_constructor = self.expr.get_ancestor(vy_ast.FunctionDef).get("name") == "__init__" if is_constructor: - memory_loc = self.context.new_internal_variable(var.typ) - setattr(var, "_metadata", {"memory_loc": memory_loc, "data_offset": 0}) + if hasattr(var, "_metadata"): + memory_loc = var._metadata["memory_loc"] + else: + memory_loc = self.context.new_internal_variable(var.typ) + data_offset = self.expr._metadata["type"].position.offset + setattr( + var, "_metadata", {"memory_loc": memory_loc, "data_offset": data_offset} + ) return LLLnode.from_list( memory_loc, typ=var.typ, From d05d987a37c41e82a966941c83fb3bb2433a6e42 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:51:10 -0400 Subject: [PATCH 32/56] fix: return size of runtime code to account for data section --- vyper/old_codegen/parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index c4ebd941ad..0833886db4 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -183,8 +183,8 @@ def parse_regular_functions( if immutables: # find position of the last immutable so we do not overwrite it in memory # when we codecopy the runtime code to memory - immutables = sorted(immutables, key=lambda imm: imm.pos) - start_pos = immutables[-1].pos + immutables[-1].size * 32 + immutables = sorted(immutables, key=lambda imm: imm._metadata["memory_loc"]) + start_pos = immutables[-1]._metadata["memory_loc"] + immutables[-1].size * 32 # create sequence of actions to copy immutables to the end of the runtime code in memory data_section = [] for immutable in immutables: @@ -199,13 +199,14 @@ def parse_regular_functions( rhs = LLLnode.from_list(memory_loc, typ=immutable.typ, location="memory") data_section.append(make_setter(lhs, rhs, None)) + data_section_size = sum([immutable.size * 32 for immutable in immutables]) o.append( [ "with", "_lllsz", # keep size of runtime bytecode in sz var ["lll", runtime, start_pos], # store runtime code at `start_pos` # sequence of copying immutables, with final action of returning the runtime code - ["seq", *data_section, ["return", start_pos, ["add", offset, "_lllsz"]]], + ["seq", *data_section, ["return", start_pos, ["add", data_section_size, "_lllsz"]]], ] ) From e1a950807ab07f15d2db83432943b5afc03b349a Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 01:57:23 -0400 Subject: [PATCH 33/56] chore: fix test parametrization --- tests/parser/features/test_immutable.py | 53 ++++++++----------------- tests/parser/syntax/test_immutables.py | 37 +++++++++-------- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index bf32f2aa20..2235bcaf83 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -1,8 +1,20 @@ import pytest -code_list = [ - ( - f""" +type_list = ( + ("uint256", 42), + ("int256", -(2 ** 200)), + ("int128", -(2 ** 126)), + ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + ("bytes32", b"deadbeef" * 4), + ("bool", True), + ("String[10]", "Vyper hiss"), + ("Bytes[10]", b"Vyper hiss"), +) + + +@pytest.mark.parametrize("typ,value", type_list) +def test_value_storage_retrieval(typ, value, get_contract): + code = f""" VALUE: immutable({typ}) @external @@ -13,40 +25,7 @@ def __init__(_value: {typ}): @external def get_value() -> {typ}: return VALUE - """, - value, - ) - for typ, value in ( - ("uint256", 42), - ("int256", -(2 ** 200)), - ("int128", -(2 ** 126)), - ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), - ("bytes32", b"deadbeef" * 4), - ("bool", True), - ("String[10]", "Vyper hiss"), - ("Bytes[10]", b"Vyper hiss"), - ) -] + """ - -@pytest.mark.parametrize("code,value", code_list) -def test_value_is_stored_correctly(code, value, get_contract): c = get_contract(code, value) assert c.get_value() == value - - -def test_simple_usage(get_contract): - code = """ -VALUE: immutable(uint256) - -@external -def __init__(_value: uint256): - VALUE = _value - -@view -@external -def get_value() -> uint256: - return VALUE -""" - c = get_contract(code, 42) - assert c.get_value() == 42 diff --git a/tests/parser/syntax/test_immutables.py b/tests/parser/syntax/test_immutables.py index 96a7064895..6f7fce29cb 100644 --- a/tests/parser/syntax/test_immutables.py +++ b/tests/parser/syntax/test_immutables.py @@ -49,8 +49,22 @@ def test_compilation_fails_with_exception(bad_code): compile_code(bad_code) -pass_list = [ - f""" +types_list = ( + "uint256", + "int256", + "int128", + "address", + "bytes32", + "decimal", + "bool", + "Bytes[64]", + "String[10]", +) + + +@pytest.mark.parametrize("typ", types_list) +def test_compilation_simple_usage(typ): + code = f""" VALUE: immutable({typ}) @external @@ -62,20 +76,11 @@ def __init__(_value: {typ}): def get_value() -> {typ}: return VALUE """ - for typ in ( - "uint256", - "int256", - "int128", - "address", - "bytes32", - "decimal", - "bool", - "Bytes[64]", - "String[10]", - ) -] -pass_list += [ + assert compile_code(code) + + +pass_list = [ # using immutable allowed in constructor """ VALUE: immutable(uint256) @@ -89,5 +94,5 @@ def __init__(_value: uint256): @pytest.mark.parametrize("good_code", pass_list) -def test_compilation_passes(good_code): +def test_compilation_success(good_code): assert compile_code(good_code) From 15dd8324a8637750bb14d428fc6c2e4b5ba8c957 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 02:54:56 -0400 Subject: [PATCH 34/56] test: multiple immutable values --- tests/parser/features/test_immutable.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index 2235bcaf83..786443e658 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -29,3 +29,25 @@ def get_value() -> {typ}: c = get_contract(code, value) assert c.get_value() == value + + +def test_multiple_immutable_values(get_contract): + code = """ +a: immutable(uint256) +b: immutable(address) +c: immutable(String[64]) + +@external +def __init__(_a: uint256, _b: address, _c: String[64]): + a = _a + b = _b + c = _c + +@view +@external +def get_values() -> (uint256, address, String[64]): + return a, b, c + """ + values = (3, "0x0000000000000000000000000000000000000000", "Hello world") + c = get_contract(code, *values) + assert c.get_values() == list(values) From 86946bfdff723b754da563233a3f38b77386bea4 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 03:53:25 -0400 Subject: [PATCH 35/56] fix(test): change dummy address used --- tests/parser/features/test_immutable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index 786443e658..137d362a10 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -48,6 +48,6 @@ def __init__(_a: uint256, _b: address, _c: String[64]): def get_values() -> (uint256, address, String[64]): return a, b, c """ - values = (3, "0x0000000000000000000000000000000000000000", "Hello world") + values = (3, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "Hello world") c = get_contract(code, *values) assert c.get_values() == list(values) From e59409944a09fbe22b53e2365e8e1b17059e565a Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 03:53:31 -0400 Subject: [PATCH 36/56] fix: allocate a new var not internal var Allocating an internal var results in deallocation once the assign operation has been parsed, resulting in the overwriting of immutable values --- vyper/old_codegen/expr.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 2176980855..3c3b86e404 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -325,11 +325,9 @@ def parse_Name(self): if hasattr(var, "_metadata"): memory_loc = var._metadata["memory_loc"] else: - memory_loc = self.context.new_internal_variable(var.typ) + memory_loc = self.context.new_variable(f"#immutable_{self.expr.id}", var.typ) data_offset = self.expr._metadata["type"].position.offset - setattr( - var, "_metadata", {"memory_loc": memory_loc, "data_offset": data_offset} - ) + var._metadata = {"memory_loc": memory_loc, "data_offset": data_offset} return LLLnode.from_list( memory_loc, typ=var.typ, From 77c4c01d2066122fc10effa9da23fa6b2ffe75f2 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 04:02:20 -0400 Subject: [PATCH 37/56] test: user defined struct immutable --- tests/parser/features/test_immutable.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index 137d362a10..c9f5f548d7 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -51,3 +51,32 @@ def get_values() -> (uint256, address, String[64]): values = (3, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "Hello world") c = get_contract(code, *values) assert c.get_values() == list(values) + + +def test_struct_immutable(get_contract): + code = """ +struct MyStruct: + a: uint256 + b: uint256 + c: address + d: int256 + +my_struct: immutable(MyStruct) + +@external +def __init__(_a: uint256, _b: uint256, _c: address, _d: int256): + my_struct = MyStruct({ + a: _a, + b: _b, + c: _c, + d: _d + }) + +@view +@external +def get_my_struct() -> MyStruct: + return my_struct + """ + values = (100, 42, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", -(2 ** 200)) + c = get_contract(code, *values) + assert c.get_my_struct() == values From d358c0705b70f93f6a66775f0d764c3c3e3ef46b Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 04:09:05 -0400 Subject: [PATCH 38/56] fix: allow immutable sequences (add kwarg to classes) --- vyper/semantics/types/bases.py | 3 ++- vyper/semantics/types/indexable/sequence.py | 11 ++++++++++- vyper/semantics/types/utils.py | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index 5692cae133..cca97a45f7 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -565,8 +565,9 @@ def __init__( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: - super().__init__(location, is_constant, is_public) + super().__init__(location, is_constant, is_public, is_immutable) self.value_type = value_type self.key_type = key_type self._id = _id diff --git a/vyper/semantics/types/indexable/sequence.py b/vyper/semantics/types/indexable/sequence.py index 59e3745cd9..fa06ba0854 100644 --- a/vyper/semantics/types/indexable/sequence.py +++ b/vyper/semantics/types/indexable/sequence.py @@ -26,6 +26,7 @@ def __init__( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: if not 0 < length < 2 ** 256: raise InvalidType("Array length is invalid") @@ -36,6 +37,7 @@ def __init__( location=location, is_constant=is_constant, is_public=is_public, + is_immutable=is_immutable, ) self.length = length @@ -62,9 +64,16 @@ def __init__( location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, + is_immutable: bool = False, ) -> None: super().__init__( - value_type, length, f"{value_type}[{length}]", location, is_constant, is_public + value_type, + length, + f"{value_type}[{length}]", + location, + is_constant, + is_public, + is_immutable, ) def __repr__(self): diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 599966d72d..7aceffbba1 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -160,8 +160,10 @@ def get_type_from_annotation( # TODO: handle `is_immutable` for arrays # if type can be an array and node is a subscript, create an `ArrayDefinition` length = get_index_value(node.slice) - value_type = get_type_from_annotation(node.value, location, is_constant, False) - return ArrayDefinition(value_type, length, location, is_constant, is_public) + value_type = get_type_from_annotation( + node.value, location, is_constant, False, is_immutable + ) + return ArrayDefinition(value_type, length, location, is_constant, is_public, is_immutable) try: return type_obj.from_annotation(node, location, is_constant, is_public, is_immutable) From f1b6d6d33751f3656f0110f7bfe0015864ddd672 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 04:09:24 -0400 Subject: [PATCH 39/56] test: immutable list usage --- tests/parser/features/test_immutable.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index c9f5f548d7..d472278b1c 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -80,3 +80,21 @@ def get_my_struct() -> MyStruct: values = (100, 42, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", -(2 ** 200)) c = get_contract(code, *values) assert c.get_my_struct() == values + + +def test_list_immutable(get_contract): + code = """ +my_list: immutable(uint256[3]) + +@external +def __init__(_a: uint256, _b: uint256, _c: uint256): + my_list = [_a, _b, _c] + +@view +@external +def get_my_list() -> uint256[3]: + return my_list + """ + values = (100, 42, 23230) + c = get_contract(code, *values) + assert c.get_my_list() == list(values) From ec13acff8166d907850b61f37b5af195651c8019 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 11:59:11 -0400 Subject: [PATCH 40/56] chore: modify test parametrization Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> --- tests/parser/features/test_immutable.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/parser/features/test_immutable.py b/tests/parser/features/test_immutable.py index d472278b1c..1ce6e1b173 100644 --- a/tests/parser/features/test_immutable.py +++ b/tests/parser/features/test_immutable.py @@ -1,18 +1,18 @@ import pytest -type_list = ( - ("uint256", 42), - ("int256", -(2 ** 200)), - ("int128", -(2 ** 126)), - ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), - ("bytes32", b"deadbeef" * 4), - ("bool", True), - ("String[10]", "Vyper hiss"), - ("Bytes[10]", b"Vyper hiss"), +@pytest.mark.parametrize( + "typ,value", + [ + ("uint256", 42), + ("int256", -(2 ** 200)), + ("int128", -(2 ** 126)), + ("address", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + ("bytes32", b"deadbeef" * 4), + ("bool", True), + ("String[10]", "Vyper hiss"), + ("Bytes[10]", b"Vyper hiss"), + ], ) - - -@pytest.mark.parametrize("typ,value", type_list) def test_value_storage_retrieval(typ, value, get_contract): code = f""" VALUE: immutable({typ}) From b95bef8a24278d55bf7056cdd4758454ef9568b7 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 12:31:22 -0400 Subject: [PATCH 41/56] docs: add immutable usage docs --- docs/scoping-and-declarations.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/scoping-and-declarations.rst b/docs/scoping-and-declarations.rst index 4749ffa5e6..8a21b51ae0 100644 --- a/docs/scoping-and-declarations.rst +++ b/docs/scoping-and-declarations.rst @@ -33,6 +33,23 @@ The compiler automatically creates getter functions for all public storage varia For public arrays, you can only retrieve a single element via the generated getter. This mechanism exists to avoid high gas costs when returning an entire array. The getter will accept an argument to specity which element to return, for example ``data(0)``. +Declaring Immutable Variables +-------------------------- + +Variables can be marked as ``immutable`` during declaration: + +.. code-block:: python + + DATA: immutable(uint256) + + @external + def __init__(_data: uint256): + DATA = _data + +Variables declared as immutable are similar to constants, except they are assigned a value in the constructor of the contract. Immutable values must be assigned a value at construction and cannot be assigned a value after construction. + +The contract creation code generated by the compiler will modify the contract’s runtime code before it is returned by appending all values assigned to immutables to the runtime code returned by the constructor. This is important if you are comparing the runtime code generated by the compiler with the one actually stored in the blockchain. + Tuple Assignment ---------------- From 14b2edb772f8c1b0a668e3408d3f9979913012a2 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 15:24:36 -0400 Subject: [PATCH 42/56] fix: allocate memory of immutable in constructor --- vyper/old_codegen/expr.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 3c3b86e404..46a6252037 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -322,12 +322,9 @@ def parse_Name(self): var = self.context.globals[self.expr.id] is_constructor = self.expr.get_ancestor(vy_ast.FunctionDef).get("name") == "__init__" if is_constructor: - if hasattr(var, "_metadata"): - memory_loc = var._metadata["memory_loc"] - else: - memory_loc = self.context.new_variable(f"#immutable_{self.expr.id}", var.typ) - data_offset = self.expr._metadata["type"].position.offset - var._metadata = {"memory_loc": memory_loc, "data_offset": data_offset} + memory_loc = self.context.new_variable(f"#immutable_{self.expr.id}", var.typ) + data_offset = self.expr._metadata["type"].position.offset + var._metadata = {"memory_loc": memory_loc, "data_offset": data_offset} return LLLnode.from_list( memory_loc, typ=var.typ, From 10c73e48d2214f1dc262ed07bfa41556a60850da Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 15:29:20 -0400 Subject: [PATCH 43/56] fix: disallow multiple assignments to immutable --- vyper/semantics/types/bases.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index cca97a45f7..ae5076c71d 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -262,6 +262,8 @@ def __init__( self.is_public = is_public self.is_immutable = is_immutable + self._modification_count = 0 + @property def canonical_type(self) -> str: """ @@ -443,8 +445,15 @@ def validate_modification(self, node: Union[vy_ast.Assign, vy_ast.AugAssign]) -> raise ImmutableViolation("Cannot write to calldata", node) if self.is_constant: raise ImmutableViolation("Immutable value cannot be written to", node) - if self.is_immutable and node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": - raise ImmutableViolation("Immutable value cannot be written to", node) + if self.is_immutable: + if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": + raise ImmutableViolation("Immutable value cannot be written to", node) + if self._modification_count: + raise ImmutableViolation( + "Immutable value cannot be modified after assignment", node + ) + self._modification_count += 1 + if isinstance(node, vy_ast.AugAssign): self.validate_numeric_op(node) From 42981abc60bd6e6be96888ba3eee8f7d38fb74cb Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Mon, 4 Oct 2021 15:29:31 -0400 Subject: [PATCH 44/56] test: multiple assignments blocked --- tests/parser/syntax/test_immutables.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/parser/syntax/test_immutables.py b/tests/parser/syntax/test_immutables.py index 6f7fce29cb..83d744d004 100644 --- a/tests/parser/syntax/test_immutables.py +++ b/tests/parser/syntax/test_immutables.py @@ -40,6 +40,16 @@ def __init__(): def set_value(_value: uint256): VALUE = _value """, + # modifying immutable multiple times in constructor + """ +VALUE: immutable(uint256) + +@external +def __init__(_value: uint256): + VALUE = _value * 3 + VALUE = VALUE + 1 + """ + ] @@ -88,7 +98,7 @@ def get_value() -> {typ}: @external def __init__(_value: uint256): VALUE = _value * 3 - VALUE = VALUE + 1 + x: uint256 = VALUE + 1 """ ] From 3e4de6e9aa4064850004d1a2fbb74cfff2d69ffa Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Tue, 12 Oct 2021 21:34:25 -0400 Subject: [PATCH 45/56] fix: set immutable data location to CODE --- vyper/semantics/validation/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/validation/module.py b/vyper/semantics/validation/module.py index 009033ed50..0a13355846 100644 --- a/vyper/semantics/validation/module.py +++ b/vyper/semantics/validation/module.py @@ -189,7 +189,7 @@ def visit_AnnAssign(self, node): # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] - data_loc = DataLocation.UNSET if is_immutable else DataLocation.STORAGE + data_loc = DataLocation.CODE if is_immutable else DataLocation.STORAGE type_definition = get_type_from_annotation( annotation, data_loc, is_constant, is_public, is_immutable ) From 48d46919e46091d0d453f9ed5605a9436f5065ec Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Tue, 12 Oct 2021 21:39:58 -0400 Subject: [PATCH 46/56] fix: verify immutable is given a single argument --- vyper/old_codegen/types/types.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/old_codegen/types/types.py b/vyper/old_codegen/types/types.py index d00963419b..8e6aa795c1 100644 --- a/vyper/old_codegen/types/types.py +++ b/vyper/old_codegen/types/types.py @@ -5,7 +5,7 @@ from typing import Any from vyper import ast as vy_ast -from vyper.exceptions import CompilerPanic, InvalidType +from vyper.exceptions import ArgumentException, CompilerPanic, InvalidType from vyper.utils import BASE_TYPES, ceil32 @@ -233,6 +233,10 @@ def parse_type(item, location=None, sigs=None, custom_structs=None): custom_structs, ) if item.func.id == "immutable": + if len(item.args) != 1: + # is checked earlier but just for sanity, verify + # immutable call is given only one argument + raise ArgumentException("Invalid number of arguments to `immutable`", item) return BaseType(item.args[0].id) raise InvalidType("Units are no longer supported", item) From b0fe0107760b5933d49cfec094d2068f5a9ea11d Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Wed, 20 Oct 2021 11:14:39 -0400 Subject: [PATCH 47/56] fix: if stmt use bool type as condition --- vyper/old_codegen/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index 0833886db4..bebd291359 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -180,7 +180,7 @@ def parse_regular_functions( immutables = [_global for _global in global_ctx._globals.values() if _global.is_immutable] - if immutables: + if len(immutables) > 0: # find position of the last immutable so we do not overwrite it in memory # when we codecopy the runtime code to memory immutables = sorted(immutables, key=lambda imm: imm._metadata["memory_loc"]) From b60ae2e91c523ced13c58a543a6a5b4963520cc0 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Wed, 20 Oct 2021 11:50:55 -0400 Subject: [PATCH 48/56] fix: make immutable data size a cached prop on global ctx --- vyper/old_codegen/expr.py | 4 +--- vyper/old_codegen/global_context.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 46a6252037..fcc37aa693 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -334,9 +334,7 @@ def parse_Name(self): mutable=True, ) else: - immutable_section_size = sum( - [imm.size * 32 for imm in self.context.globals.values() if imm.is_immutable] - ) + immutable_section_size = self.context.global_ctx.immutable_section_size offset = self.expr._metadata["type"].position.offset return LLLnode.from_list( ["sub", "codesize", immutable_section_size - offset], diff --git a/vyper/old_codegen/global_context.py b/vyper/old_codegen/global_context.py index a9a13acce3..f96d229fb8 100644 --- a/vyper/old_codegen/global_context.py +++ b/vyper/old_codegen/global_context.py @@ -6,6 +6,12 @@ from vyper.old_codegen.types import InterfaceType, parse_type from vyper.typing import InterfaceImports +try: + # available py3.8+ + from functools import cached_property +except ImportError: + from cached_property import cached_property # type: ignore + # Datatype to store all global context information. class GlobalContext: @@ -235,3 +241,7 @@ def parse_type(self, ast_node, location=None): sigs=self._contracts, custom_structs=self._structs, ) + + @cached_property + def immutable_section_size(self): + return sum([imm.size * 32 for imm in self._globals.values() if imm.is_immutable]) From 9d04be1ec338e164f2bb85b5bf18e08fe3529341 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 21 Oct 2021 12:48:50 -0400 Subject: [PATCH 49/56] fix: swap 'lll' arguments, offset is first code is second --- tests/compiler/LLL/test_compile_lll.py | 3 ++- vyper/compiler/utils.py | 2 +- vyper/lll/compile_lll.py | 4 ++-- vyper/old_codegen/parser.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/compiler/LLL/test_compile_lll.py b/tests/compiler/LLL/test_compile_lll.py index dfde17dc44..f9d51a1f5a 100644 --- a/tests/compiler/LLL/test_compile_lll.py +++ b/tests/compiler/LLL/test_compile_lll.py @@ -41,12 +41,13 @@ def test_lll_from_s_expression(get_contract_from_lll): (return 0 (lll ; just return 32 byte of calldata back + 0 (seq (calldatacopy 0 4 32) (return 0 32) stop ) - 0))) + ))) """ abi = [ { diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index da1a449ec7..83b1599b2b 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -10,7 +10,7 @@ def build_gas_estimates(lll_nodes: LLLnode) -> dict: and len(lll_nodes.args) > 0 and lll_nodes.args[-1].value == "return" ): - lll_nodes = lll_nodes.args[-1].args[1].args[0] + lll_nodes = lll_nodes.args[-1].args[1].args[1] external_sub = next((i for i in lll_nodes.args if i.value == "with"), None) if external_sub: diff --git a/vyper/lll/compile_lll.py b/vyper/lll/compile_lll.py index d86a4f3994..0b02e3ce34 100644 --- a/vyper/lll/compile_lll.py +++ b/vyper/lll/compile_lll.py @@ -291,7 +291,7 @@ def _compile_to_assembly(code, withargs=None, existing_labels=None, break_dest=N endcode = mksymbol() o.extend([endcode, "JUMP", begincode, "BLANK"]) - lll = _compile_to_assembly(code.args[0], {}, existing_labels, None, 0) + lll = _compile_to_assembly(code.args[1], {}, existing_labels, None, 0) # `append(...)` call here is intentional. # each sublist is essentially its own program with its @@ -303,7 +303,7 @@ def _compile_to_assembly(code, withargs=None, existing_labels=None, break_dest=N o.append(lll) o.extend([endcode, "JUMPDEST", begincode, endcode, "SUB", begincode]) - o.extend(_compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height)) + o.extend(_compile_to_assembly(code.args[0], withargs, existing_labels, break_dest, height)) # COPY the code to memory for deploy o.extend(["CODECOPY", begincode, endcode, "SUB"]) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index bebd291359..82d756abb8 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -204,7 +204,7 @@ def parse_regular_functions( [ "with", "_lllsz", # keep size of runtime bytecode in sz var - ["lll", runtime, start_pos], # store runtime code at `start_pos` + ["lll", start_pos, runtime], # store runtime code at `start_pos` # sequence of copying immutables, with final action of returning the runtime code ["seq", *data_section, ["return", start_pos, ["add", data_section_size, "_lllsz"]]], ] @@ -214,7 +214,7 @@ def parse_regular_functions( # NOTE: lll macro trailing 0 is the location in memory to store # the compiled bytecode # https://lll-docs.readthedocs.io/en/latest/lll_reference.html#code-lll - o.append(["return", 0, ["lll", runtime, 0]]) + o.append(["return", 0, ["lll", 0, runtime]]) return o, runtime From 99217d99ae38b23df4a19edc9eb18e44af6b97ac Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Thu, 21 Oct 2021 13:05:26 -0400 Subject: [PATCH 50/56] fix: remove _metadata dict on LLLnode for immutables Instead when parsing the immutable modify the variable record in the global ctx --- vyper/ast/signatures/function_signature.py | 3 +++ vyper/old_codegen/expr.py | 6 +++++- vyper/old_codegen/parser.py | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/vyper/ast/signatures/function_signature.py b/vyper/ast/signatures/function_signature.py index 2dc6a9d2a1..c86312d747 100644 --- a/vyper/ast/signatures/function_signature.py +++ b/vyper/ast/signatures/function_signature.py @@ -1,5 +1,6 @@ import math from dataclasses import dataclass +from typing import Optional from vyper import ast as vy_ast from vyper.exceptions import StructureException @@ -24,6 +25,7 @@ def __init__( defined_at=None, is_internal=False, is_immutable=False, + data_offset: Optional[int] = None, ): self.name = name self.pos = pos @@ -35,6 +37,7 @@ def __init__( self.defined_at = defined_at # source code location variable record was defined. self.is_internal = is_internal self.is_immutable = is_immutable + self.data_offset = data_offset # location in data section def __repr__(self): ret = vars(self) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index fcc37aa693..761f5b1362 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -322,9 +322,13 @@ def parse_Name(self): var = self.context.globals[self.expr.id] is_constructor = self.expr.get_ancestor(vy_ast.FunctionDef).get("name") == "__init__" if is_constructor: + # store memory position for later access in parser.py in the variable record memory_loc = self.context.new_variable(f"#immutable_{self.expr.id}", var.typ) + self.context.global_ctx._globals[self.expr.id].pos = memory_loc + # store the data offset in the variable record as well for accessing data_offset = self.expr._metadata["type"].position.offset - var._metadata = {"memory_loc": memory_loc, "data_offset": data_offset} + self.context.global_ctx._globals[self.expr.id].data_offset = data_offset + return LLLnode.from_list( memory_loc, typ=var.typ, diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index 82d756abb8..020d11bd1f 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -183,15 +183,15 @@ def parse_regular_functions( if len(immutables) > 0: # find position of the last immutable so we do not overwrite it in memory # when we codecopy the runtime code to memory - immutables = sorted(immutables, key=lambda imm: imm._metadata["memory_loc"]) - start_pos = immutables[-1]._metadata["memory_loc"] + immutables[-1].size * 32 + immutables = sorted(immutables, key=lambda imm: imm.pos) + start_pos = immutables[-1].pos + immutables[-1].size * 32 # create sequence of actions to copy immutables to the end of the runtime code in memory data_section = [] for immutable in immutables: # store each immutable at the end of the runtime code memory_loc, offset = ( - immutable._metadata["memory_loc"], - immutable._metadata["data_offset"], + immutable.pos, + immutable.data_offset, ) lhs = LLLnode.from_list( ["add", start_pos + offset, "_lllsz"], typ=immutable.typ, location="memory" From 7c0127522160c35f1a7b2635a18ee438f9d7a645 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Fri, 12 Nov 2021 07:24:41 +0000 Subject: [PATCH 51/56] fix: mypy typing error --- vyper/ast/signatures/function_signature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ast/signatures/function_signature.py b/vyper/ast/signatures/function_signature.py index c86312d747..31394122b0 100644 --- a/vyper/ast/signatures/function_signature.py +++ b/vyper/ast/signatures/function_signature.py @@ -13,7 +13,7 @@ # TODO move to context.py # TODO use dataclass class VariableRecord: - def __init__( + def __init__( # type: ignore self, name, pos, From b55dfe1e8f317980845270872810413a47bfe978 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Fri, 12 Nov 2021 18:16:08 +0000 Subject: [PATCH 52/56] Update vyper/semantics/types/indexable/sequence.py Co-authored-by: Charles Cooper --- vyper/semantics/types/indexable/sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/types/indexable/sequence.py b/vyper/semantics/types/indexable/sequence.py index fa06ba0854..118f34b8d0 100644 --- a/vyper/semantics/types/indexable/sequence.py +++ b/vyper/semantics/types/indexable/sequence.py @@ -120,7 +120,7 @@ class TupleDefinition(_SequenceDefinition): def __init__(self, value_type: Tuple[BaseTypeDefinition, ...]) -> None: # always use the most restrictive location re: modification location = sorted((i.location for i in value_type), key=lambda k: k.value)[-1] - is_constant = next((True for i in value_type if getattr(i, "is_constant", None)), False) + is_constant = any((getattr(i, "is_constant", False) for i in value_type)) super().__init__( # TODO fix the typing on value_type value_type, # type: ignore From 502bb6aedb93d0fd8004d4d598965560913a3307 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Fri, 12 Nov 2021 18:16:20 +0000 Subject: [PATCH 53/56] Update vyper/semantics/types/bases.py Co-authored-by: Charles Cooper --- vyper/semantics/types/bases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/types/bases.py b/vyper/semantics/types/bases.py index ae5076c71d..c65e18bcfa 100644 --- a/vyper/semantics/types/bases.py +++ b/vyper/semantics/types/bases.py @@ -444,7 +444,7 @@ def validate_modification(self, node: Union[vy_ast.Assign, vy_ast.AugAssign]) -> if self.location == DataLocation.CALLDATA: raise ImmutableViolation("Cannot write to calldata", node) if self.is_constant: - raise ImmutableViolation("Immutable value cannot be written to", node) + raise ImmutableViolation("Constant value cannot be written to", node) if self.is_immutable: if node.get_ancestor(vy_ast.FunctionDef).get("name") != "__init__": raise ImmutableViolation("Immutable value cannot be written to", node) From 90c859499cb625decacc05149b5446ffeaa9ecc0 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Fri, 12 Nov 2021 18:14:09 +0000 Subject: [PATCH 54/56] fix: use cached property from vyper.utils --- vyper/old_codegen/global_context.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vyper/old_codegen/global_context.py b/vyper/old_codegen/global_context.py index f96d229fb8..436e99dcf5 100644 --- a/vyper/old_codegen/global_context.py +++ b/vyper/old_codegen/global_context.py @@ -5,12 +5,7 @@ from vyper.exceptions import CompilerPanic, InvalidType, StructureException from vyper.old_codegen.types import InterfaceType, parse_type from vyper.typing import InterfaceImports - -try: - # available py3.8+ - from functools import cached_property -except ImportError: - from cached_property import cached_property # type: ignore +from vyper.utils import cached_property # Datatype to store all global context information. From 713a7bd2c481222f556c32d16d925a3b6aa4fc10 Mon Sep 17 00:00:00 2001 From: Edward Amor Date: Fri, 12 Nov 2021 18:26:43 +0000 Subject: [PATCH 55/56] chore: leave todo, come back and resolve immutable offsets at compile time --- vyper/old_codegen/expr.py | 1 + vyper/old_codegen/parser.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/vyper/old_codegen/expr.py b/vyper/old_codegen/expr.py index 761f5b1362..5251c1126c 100644 --- a/vyper/old_codegen/expr.py +++ b/vyper/old_codegen/expr.py @@ -340,6 +340,7 @@ def parse_Name(self): else: immutable_section_size = self.context.global_ctx.immutable_section_size offset = self.expr._metadata["type"].position.offset + # TODO: resolve code offsets for immutables at compile time return LLLnode.from_list( ["sub", "codesize", immutable_section_size - offset], typ=var.typ, diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index 020d11bd1f..ae28146cb7 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -180,6 +180,8 @@ def parse_regular_functions( immutables = [_global for _global in global_ctx._globals.values() if _global.is_immutable] + # TODO: enable usage of the data section beyond just user defined immutables + # https://github.com/vyperlang/vyper/pull/2466#discussion_r722816358 if len(immutables) > 0: # find position of the last immutable so we do not overwrite it in memory # when we codecopy the runtime code to memory From 9bd3b455268129ffd172f2d1ce6c2bc9faac63aa Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 12 Nov 2021 16:20:40 -0800 Subject: [PATCH 56/56] update a comment about lll macro --- vyper/old_codegen/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/old_codegen/parser.py b/vyper/old_codegen/parser.py index ae28146cb7..08e5fb23b1 100644 --- a/vyper/old_codegen/parser.py +++ b/vyper/old_codegen/parser.py @@ -213,7 +213,7 @@ def parse_regular_functions( ) else: - # NOTE: lll macro trailing 0 is the location in memory to store + # NOTE: lll macro first argument is the location in memory to store # the compiled bytecode # https://lll-docs.readthedocs.io/en/latest/lll_reference.html#code-lll o.append(["return", 0, ["lll", 0, runtime]])