def fetch_call_return(self, node: vy_ast.Call) -> Optional[BaseTypeDefinition]: if node.get("func.value.id") == "self" and self.visibility == FunctionVisibility.EXTERNAL: raise CallViolation("Cannnot call external functions via 'self'", node) # for external calls, include gas and value as optional kwargs kwarg_keys = self.kwarg_keys.copy() if node.get("func.value.id") != "self": kwarg_keys += ["gas", "value"] validate_call_args(node, (self.min_arg_count, self.max_arg_count), kwarg_keys) if self.mutability < StateMutability.PAYABLE: kwarg_node = next((k for k in node.keywords if k.arg == "value"), None) if kwarg_node is not None: raise CallViolation("Cannnot send ether to nonpayable function", kwarg_node) for arg, expected in zip(node.args, self.arguments.values()): validate_expected_type(arg, expected) for kwarg in node.keywords: if kwarg.arg in ("gas", "value"): validate_expected_type(kwarg.value, Uint256Definition()) else: validate_expected_type(kwarg.arg, kwarg.value) return self.return_type
def __init__( self, name: str, arguments: OrderedDict, # TODO rename to something like positional_args, keyword_args min_arg_count: int, max_arg_count: int, return_type: Optional[BaseTypeDefinition], function_visibility: FunctionVisibility, state_mutability: StateMutability, nonreentrant: Optional[str] = None, ) -> None: super().__init__( # A function definition type only exists while compiling DataLocation.UNSET, # A function definition type is immutable once created is_constant=True, # A function definition type is public if it's visibility is public is_public=(function_visibility == FunctionVisibility.EXTERNAL), ) self.name = name self.arguments = arguments self.min_arg_count = min_arg_count self.max_arg_count = max_arg_count self.return_type = return_type self.kwarg_keys = [] if min_arg_count < max_arg_count: self.kwarg_keys = list(self.arguments)[min_arg_count:] self.visibility = function_visibility self.mutability = state_mutability self.nonreentrant = nonreentrant # a list of internal functions this function calls self.called_functions: Set["ContractFunction"] = set() # special kwargs that are allowed in call site self.call_site_kwargs = { "gas": KwargSettings(Uint256Definition(), "gas"), "value": KwargSettings(Uint256Definition(), 0), "skip_contract_check": KwargSettings(BoolDefinition(), False, require_literal=True), "default_return_value": KwargSettings(return_type, None), }
def get_signature(self) -> Tuple[Tuple, Optional[BaseTypeDefinition]]: # override the default behavior to return `Uint256Definition` # an external interface cannot use `IntegerAbstractType` because # abstract types have no canonical type new_args, return_type = self.value_type.get_signature() return (Uint256Definition(), ) + new_args, return_type
def pack_logging_data(arg_nodes, arg_types, context, pos): # Checks to see if there's any data if not arg_nodes: return ["seq"], 0, None, 0 holder = ["seq"] maxlen = len(arg_nodes) * 32 # total size of all packed args (upper limit) # Unroll any function calls, to temp variables. prealloacted = {} for idx, node in enumerate(arg_nodes): if isinstance( node, (vy_ast.Str, vy_ast.Call)) and node.get("func.id") != "empty": expr = Expr(node, context) source_lll = expr.lll_node tmp_variable = context.new_internal_variable(source_lll.typ) tmp_variable_node = LLLnode.from_list( tmp_variable, typ=source_lll.typ, pos=getpos(node), location="memory", annotation=f"log_prealloacted {source_lll.typ}", ) # Copy bytes. holder.append( make_setter(tmp_variable_node, source_lll, pos=getpos(node), location="memory")) prealloacted[idx] = tmp_variable_node # Create internal variables for for dynamic and static args. static_types = [] for typ in arg_types: static_types.append( typ if not typ.is_dynamic_size else Uint256Definition()) requires_dynamic_offset = any(typ.is_dynamic_size for typ in arg_types) dynamic_offset_counter = None if requires_dynamic_offset: # TODO refactor out old type objects dynamic_offset_counter = context.new_internal_variable(BaseType(32)) dynamic_placeholder = context.new_internal_variable(BaseType(32)) static_vars = [context.new_internal_variable(i) for i in static_types] # Populate static placeholders. for i, (node, typ) in enumerate(zip(arg_nodes, arg_types)): placeholder = static_vars[i] if not isinstance(typ, ArrayValueAbstractType): holder, maxlen = pack_args_by_32( holder, maxlen, prealloacted.get(i, node), typ, context, placeholder, pos=pos, ) # Dynamic position starts right after the static args. if requires_dynamic_offset: holder.append( LLLnode.from_list(["mstore", dynamic_offset_counter, maxlen])) # Calculate maximum dynamic offset placeholders, used for gas estimation. for typ in arg_types: if typ.is_dynamic_size: maxlen += typ.size_in_bytes if requires_dynamic_offset: datamem_start = dynamic_placeholder + 32 else: datamem_start = static_vars[0] # Copy necessary data into allocated dynamic section. for i, (node, typ) in enumerate(zip(arg_nodes, arg_types)): if isinstance(typ, ArrayValueAbstractType): if isinstance(node, vy_ast.Call) and node.func.get("id") == "empty": # TODO add support for this raise StructureException( "Cannot use `empty` on Bytes or String types within an event log", node) pack_args_by_32( holder=holder, maxlen=maxlen, arg=prealloacted.get(i, node), typ=typ, context=context, placeholder=static_vars[i], datamem_start=datamem_start, dynamic_offset_counter=dynamic_offset_counter, pos=pos, ) return holder, maxlen, dynamic_offset_counter, datamem_start
def get_index_type(self) -> BaseTypeDefinition: # override the default behaviour to return `Uint256Definition` for # type annotation return Uint256Definition()
def visit_For(self, node): if isinstance(node.iter, vy_ast.Subscript): raise StructureException("Cannot iterate over a nested list", node.iter) if isinstance(node.iter, vy_ast.Call): # iteration via range() if node.iter.get("func.id") != "range": raise IteratorException( "Cannot iterate over the result of a function call", node.iter) validate_call_args(node.iter, (1, 2)) args = node.iter.args if len(args) == 1: # range(CONSTANT) if not isinstance(args[0], vy_ast.Num): raise StateAccessViolation("Value must be a literal", node) if args[0].value <= 0: raise StructureException( "For loop must have at least 1 iteration", args[0]) validate_expected_type(args[0], Uint256Definition()) type_list = get_possible_types_from_node(args[0]) else: validate_expected_type(args[0], IntegerAbstractType()) type_list = get_common_types(*args) if not isinstance(args[0], vy_ast.Constant): # range(x, x + CONSTANT) if not isinstance(args[1], vy_ast.BinOp) or not isinstance( args[1].op, vy_ast.Add): raise StructureException( "Second element must be the first element plus a literal value", args[0], ) if not vy_ast.compare_nodes(args[0], args[1].left): raise StructureException( "First and second variable must be the same", args[1].left) if not isinstance(args[1].right, vy_ast.Int): raise InvalidLiteral("Literal must be an integer", args[1].right) if args[1].right.value < 1: raise StructureException( f"For loop has invalid number of iterations ({args[1].right.value})," " the value must be greater than zero", args[1].right, ) else: # range(CONSTANT, CONSTANT) if not isinstance(args[1], vy_ast.Int): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerAbstractType()) if args[0].value >= args[1].value: raise StructureException( "Second value must be > first value", args[1]) else: # iteration over a variable or literal list type_list = [ i.value_type for i in get_possible_types_from_node(node.iter) if isinstance(i, ArrayDefinition) ] if not type_list: raise InvalidType("Not an iterable type", node.iter) if next((i for i in type_list if isinstance(i, ArrayDefinition)), False): raise StructureException("Cannot iterate over a nested list", node.iter) if next((i for i in type_list if isinstance(i, StructDefinition)), False): raise StructureException("Cannot iterate over a list of structs", node.iter) if isinstance(node.iter, (vy_ast.Name, vy_ast.Attribute)): # check for references to the iterated value within the body of the loop assign = _check_iterator_assign(node.iter, node) if assign: raise ImmutableViolation( "Cannot modify array during iteration", assign) if node.iter.get("value.id") == "self": # check if iterated value may be modified by function calls inside the loop iter_name = node.iter.attr for call_node in node.get_descendants(vy_ast.Call, {"func.value.id": "self"}): fn_name = call_node.func.attr fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": fn_name})[0] if _check_iterator_assign(node.iter, fn_node): # check for direct modification raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it potentially " f"modifies iterated storage variable '{iter_name}'", call_node, ) for name in self.namespace["self"].members[ fn_name].recursive_calls: # check for indirect modification fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": name})[0] if _check_iterator_assign(node.iter, fn_node): raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it may call to '{name}' " f"which potentially modifies iterated storage variable '{iter_name}'", call_node, ) for_loop_exceptions = [] iter_name = node.target.id 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 with self.namespace.enter_scope(): try: self.namespace[iter_name] = type_ except VyperException as exc: raise exc.with_annotation(node) from None try: for n in node.body: self.visit(n) # type information is applied directly because the scope is # closed prior to the call to `StatementAnnotationVisitor` node.target._metadata["type"] = type_ return except (TypeMismatch, InvalidOperation) as exc: for_loop_exceptions.append(exc) if len(set(str(i) for i in for_loop_exceptions)) == 1: # if every attempt at type checking raised the same exception raise for_loop_exceptions[0] # return an aggregate TypeMismatch that shows all possible exceptions # depending on which type is used types_str = [str(i) for i in type_list] given_str = f"{', '.join(types_str[:1])} or {types_str[-1]}" raise TypeMismatch( f"Iterator value '{iter_name}' may be cast as {given_str}, " "but type checking fails with all possible types:", node, *((f"Casting '{iter_name}' as {type_}: {exc.message}", exc.annotations[0]) for type_, exc in zip(type_list, for_loop_exceptions)), )