def add_ofst(loc, ofst): ofst = LLLnode.from_list(ofst) if isinstance(loc.value, int) and isinstance(ofst.value, int): ret = loc.value + ofst.value else: ret = ["add", loc, ofst] return LLLnode.from_list(ret, location=loc.location, encoding=loc.encoding)
def pop_dyn_array(darray_node, return_popped_item, pos=None): assert isinstance(darray_node.typ, DArrayType) ret = ["seq"] with darray_node.cache_when_complex("darray") as (b1, darray_node): old_len = ["clamp_nonzero", get_dyn_array_count(darray_node)] new_len = LLLnode.from_list(["sub", old_len, 1], typ="uint256") with new_len.cache_when_complex("new_len") as (b2, new_len): ret.append([store_op(darray_node.location), darray_node, new_len]) # NOTE skip array bounds check bc we already asserted len two lines up if return_popped_item: popped_item = get_element_ptr(darray_node, new_len, array_bounds_check=False, pos=pos) ret.append(popped_item) typ = popped_item.typ location = popped_item.location encoding = popped_item.encoding else: typ, location, encoding = None, None, None return LLLnode.from_list(b1.resolve(b2.resolve(ret)), typ=typ, location=location, encoding=encoding, pos=pos)
def _complex_make_setter(left, right, pos): if right.value == "~empty" and left.location == "memory": # optimized memzero return mzero(left, left.typ.memory_bytes_required) ret = ["seq"] if isinstance(left.typ, SArrayType): n_items = right.typ.count keys = [LLLnode.from_list(i, typ="uint256") for i in range(n_items)] if isinstance(left.typ, TupleLike): keys = left.typ.tuple_keys() # if len(keyz) == 0: # return LLLnode.from_list(["pass"]) # general case # TODO use copy_bytes when the generated code is above a certain size with left.cache_when_complex("_L") as ( b1, left), right.cache_when_complex("_R") as (b2, right): for k in keys: l_i = get_element_ptr(left, k, pos=pos, array_bounds_check=False) r_i = get_element_ptr(right, k, pos=pos, array_bounds_check=False) ret.append(make_setter(l_i, r_i, pos)) return b1.resolve(b2.resolve(LLLnode.from_list(ret)))
def generate_lll_for_internal_function(code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a internal function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :param context: current calling context :return: function body in LLL """ # The calling convention is: # Caller fills in argument buffer # Caller provides return address, return buffer on the stack # Callee runs its code, fills in return buffer provided by caller # Callee jumps back to caller # The reason caller fills argument buffer is so there is less # complication with passing args on the stack; the caller is better # suited to optimize the copy operation. Also it avoids the callee # having to handle default args; that is easier left to the caller # as well. Meanwhile, the reason the callee fills the return buffer # is first, similarly, the callee is more suited to optimize the copy # operation. Second, it allows the caller to allocate the return # buffer in a way which reduces the number of copies. Third, it # reduces the potential for bugs since it forces the caller to have # the return data copied into a preallocated location. Otherwise, a # situation like the following is easy to bork: # x: T[2] = [self.generate_T(), self.generate_T()] func_type = code._metadata["type"] # Get nonreentrant lock for arg in sig.args: # allocate a variable for every arg, setting mutability # to False to comply with vyper semantics, function arguments are immutable context.new_variable(arg.name, arg.typ, is_mutable=False) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) function_entry_label = sig.internal_function_label cleanup_label = sig.exit_sequence_label # jump to the label which was passed in via stack stop_func = LLLnode.from_list(["jump", "pass"], annotation="jump to return address") enter = [["label", function_entry_label]] + nonreentrant_pre body = [parse_body(c, context) for c in code.body] exit = [["label", cleanup_label]] + nonreentrant_post + [stop_func] return LLLnode.from_list( ["seq"] + enter + body + exit, typ=None, pos=getpos(code), )
def _register_function_args(context: Context, sig: FunctionSignature) -> List[LLLnode]: pos = None ret = [] # the type of the calldata base_args_t = TupleType([arg.typ for arg in sig.base_args]) # tuple with the abi_encoded args if sig.is_init_func: base_args_ofst = LLLnode(0, location="data", typ=base_args_t, encoding=Encoding.ABI) else: base_args_ofst = LLLnode(4, location="calldata", typ=base_args_t, encoding=Encoding.ABI) for i, arg in enumerate(sig.base_args): arg_lll = get_element_ptr(base_args_ofst, i, pos=pos) if _should_decode(arg.typ): # allocate a memory slot for it and copy p = context.new_variable(arg.name, arg.typ, is_mutable=False) dst = LLLnode(p, typ=arg.typ, location="memory") ret.append(make_setter(dst, arg_lll, pos=pos)) else: # leave it in place context.vars[arg.name] = VariableRecord( name=arg.name, pos=arg_lll, typ=arg.typ, mutable=False, location=arg_lll.location, encoding=Encoding.ABI, ) return ret
def parse_Name(self): if self.expr.id == "self": return LLLnode.from_list(["address"], typ="address", pos=getpos(self.expr)) elif self.expr.id in self.context.vars: var = self.context.vars[self.expr.id] return LLLnode.from_list( var.pos, typ=var.typ, location=var. location, # either 'memory' or 'calldata' storage is handled above. encoding=var.encoding, pos=getpos(self.expr), annotation=self.expr.id, mutable=var.mutable, ) elif self.expr.id in BUILTIN_CONSTANTS: obj, typ = BUILTIN_CONSTANTS[self.expr.id] 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: # store memory position for later access in module.py in the variable record memory_loc = self.context.new_variable(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 self.context.global_ctx._globals[ self.expr.id].data_offset = data_offset return LLLnode.from_list( memory_loc, typ=var.typ, location="memory", pos=getpos(self.expr), annotation=self.expr.id, mutable=True, ) 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, location="code", pos=getpos(self.expr), annotation=self.expr.id, mutable=False, )
def _mul(x, y): x = LLLnode.from_list(x) y = LLLnode.from_list(y) if isinstance(x.value, int) and isinstance(y.value, int): ret = x.value * y.value else: ret = ["mul", x, y] return LLLnode.from_list(ret)
def unwrap_location(orig): if orig.location in ("memory", "storage", "calldata", "code"): return LLLnode.from_list([load_op(orig.location), orig], typ=orig.typ) else: # CMC 20210909 TODO double check if this branch can be removed if orig.value == "~empty": return LLLnode.from_list(0, typ=orig.typ) return orig
def _get_element_ptr_tuplelike(parent, key, pos): typ = parent.typ assert isinstance(typ, TupleLike) if isinstance(typ, StructType): assert isinstance(key, str) subtype = typ.members[key] attrs = list(typ.tuple_keys()) index = attrs.index(key) annotation = key else: assert isinstance(key, int) subtype = typ.members[key] attrs = list(range(len(typ.members))) index = key annotation = None # generated by empty() + make_setter if parent.value == "~empty": return LLLnode.from_list("~empty", typ=subtype) if parent.value == "multi": assert parent.encoding != Encoding.ABI, "no abi-encoded literals" return parent.args[index] ofst = 0 # offset from parent start if parent.encoding in (Encoding.ABI, Encoding.JSON_ABI): if parent.location == "storage": raise CompilerPanic("storage variables should not be abi encoded" ) # pragma: notest member_t = typ.members[attrs[index]] for i in range(index): member_abi_t = typ.members[attrs[i]].abi_type ofst += member_abi_t.embedded_static_size() return _getelemptr_abi_helper(parent, member_t, ofst, pos) if parent.location == "storage": for i in range(index): ofst += typ.members[attrs[i]].storage_size_in_words elif parent.location in ("calldata", "memory", "code"): for i in range(index): ofst += typ.members[attrs[i]].memory_bytes_required else: raise CompilerPanic( f"bad location {parent.location}") # pragma: notest return LLLnode.from_list( add_ofst(parent, ofst), typ=subtype, location=parent.location, encoding=parent.encoding, annotation=annotation, pos=pos, )
def parse_tree_to_lll(global_ctx: GlobalContext) -> Tuple[LLLnode, LLLnode, FunctionSignatures]: _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _names_def if _names_def.count(name) > 1][0]}" ) _names_events = [_event.name for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException( f"""Duplicate event name: {[name for name in _names_events if _names_events.count(name) > 1][0]}""" ) # Initialization function init_function = next((_def for _def in global_ctx._defs if is_initializer(_def)), None) # Default function default_function = next((i for i in global_ctx._defs if is_default_func(i)), None) regular_functions = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs: dict = {} external_interfaces: dict = {} # Create the main statement o: List[Union[str, LLLnode]] = ["seq"] if global_ctx._contracts or global_ctx._interfaces: external_interfaces = parse_external_interfaces(external_interfaces, global_ctx) # TODO: fix for #2251 is to move this after parse_regular_functions if init_function: o.append(init_func_init_lll()) init_func_lll, _frame_start, _frame_size = generate_lll_for_function( init_function, {**{"self": sigs}, **external_interfaces}, global_ctx, False, ) o.append(init_func_lll) if regular_functions or default_function: o, runtime = parse_regular_functions( o, regular_functions, sigs, external_interfaces, global_ctx, default_function, ) else: runtime = o.copy() return LLLnode.from_list(o), LLLnode.from_list(runtime), sigs
def parse_List(self): pos = getpos(self.expr) typ = new_type_to_old_type(self.expr._metadata["type"]) if len(self.expr.elements) == 0: return LLLnode.from_list("~empty", typ=typ, pos=pos) multi_lll = [ Expr(x, self.context).lll_node for x in self.expr.elements ] return LLLnode.from_list(["multi"] + multi_lll, typ=typ, pos=pos)
def finalize(fill_return_buffer): # do NOT bypass this. jump_to_exit may do important function cleanup. fill_return_buffer = LLLnode.from_list( fill_return_buffer, annotation=f"fill return buffer {sig._lll_identifier}") cleanup_loops = "cleanup_repeat" if context.forvars else "pass" return LLLnode.from_list( ["seq", cleanup_loops, fill_return_buffer, jump_to_exit], typ=None, pos=_pos, )
def finalize(fill_return_buffer): # do NOT bypass this. jump_to_exit may do important function cleanup. fill_return_buffer = LLLnode.from_list( fill_return_buffer, annotation=f"fill return buffer {sig._lll_identifier}") cleanup_loops = "cleanup_repeat" if context.forvars else "pass" # NOTE: because stack analysis is incomplete, cleanup_repeat must # come after fill_return_buffer otherwise the stack will break return LLLnode.from_list( ["seq", fill_return_buffer, cleanup_loops, jump_to_exit], pos=_pos, )
def get_dyn_array_count(arg): assert isinstance(arg.typ, DArrayType) typ = BaseType("uint256") if arg.value == "multi": return LLLnode.from_list(len(arg.args), typ=typ) if arg.value == "~empty": # empty(DynArray[]) return LLLnode.from_list(0, typ=typ) return LLLnode.from_list([load_op(arg.location), arg], typ=typ)
def parse_NameConstant(self): if self.expr.value is True: return LLLnode.from_list( 1, typ=BaseType("bool", is_literal=True), pos=getpos(self.expr), ) elif self.expr.value is False: return LLLnode.from_list( 0, typ=BaseType("bool", is_literal=True), pos=getpos(self.expr), )
def parse_Hex(self): orignum = self.expr.value if len(orignum) == 42 and checksum_encode(orignum) == orignum: return LLLnode.from_list( int(self.expr.value, 16), typ=BaseType("address", is_literal=True), pos=getpos(self.expr), ) elif len(orignum) == 66: return LLLnode.from_list( int(self.expr.value, 16), typ=BaseType("bytes32", is_literal=True), pos=getpos(self.expr), )
def ensure_in_memory(lll_var, context, pos=None): """Ensure a variable is in memory. This is useful for functions which expect to operate on memory variables. """ if lll_var.location == "memory": return lll_var typ = lll_var.typ buf = LLLnode.from_list(context.new_internal_variable(typ), typ=typ, location="memory") do_copy = make_setter(buf, lll_var, pos=pos) return LLLnode.from_list(["seq", do_copy, buf], typ=typ, location="memory")
def parse_Int(self): # Literal (mostly likely) becomes int256 if self.expr.n < 0: return LLLnode.from_list( self.expr.n, typ=BaseType("int256", is_literal=True), pos=getpos(self.expr), ) # Literal is large enough (mostly likely) becomes uint256. else: return LLLnode.from_list( self.expr.n, typ=BaseType("uint256", is_literal=True), pos=getpos(self.expr), )
def append_dyn_array(darray_node, elem_node, pos=None): assert isinstance(darray_node.typ, DArrayType) assert darray_node.typ.count > 0, "jerk boy u r out" ret = ["seq"] with darray_node.cache_when_complex("darray") as (b1, darray_node): len_ = get_dyn_array_count(darray_node) with len_.cache_when_complex("old_darray_len") as (b2, len_): ret.append(["assert", ["le", len_, darray_node.typ.count - 1]]) ret.append([ store_op(darray_node.location), darray_node, ["add", len_, 1] ]) # NOTE: typechecks elem_node # NOTE skip array bounds check bc we already asserted len two lines up ret.append( make_setter( get_element_ptr(darray_node, len_, array_bounds_check=False, pos=pos), elem_node, pos=pos, )) return LLLnode.from_list(b1.resolve(b2.resolve(ret)), pos=pos)
def _rewrite_return_sequences(lll_node, label_params=None): args = lll_node.args if lll_node.value == "return": if args[0].value == "ret_ofst" and args[1].value == "ret_len": lll_node.args[0].value = "pass" lll_node.args[1].value = "pass" if lll_node.value == "exit_to": # handle exit from private function if args[0].value == "return_pc": lll_node.value = "jump" args[0].value = "pass" else: # handle jump to cleanup assert is_symbol(args[0].value) lll_node.value = "seq" _t = ["seq"] if "return_buffer" in label_params: _t.append(["pop", "pass"]) dest = args[0].value[5:] # `_sym_foo` -> `foo` more_args = ["pass" if t.value == "return_pc" else t for t in args[1:]] _t.append(["goto", dest] + more_args) lll_node.args = LLLnode.from_list(_t, pos=lll_node.pos).args if lll_node.value == "label": label_params = set(t.value for t in lll_node.args[1].args) for t in args: _rewrite_return_sequences(t, label_params)
def _getelemptr_abi_helper(parent, member_t, ofst, pos=None, clamp=True): member_abi_t = member_t.abi_type # ABI encoding has length word and then pretends length is not there # e.g. [[1,2]] is encoded as 0x01 <len> 0x20 <inner array ofst> <encode(inner array)> # note that inner array ofst is 0x20, not 0x40. if has_length_word(parent.typ): parent = add_ofst(parent, wordsize(parent.location) * DYNAMIC_ARRAY_OVERHEAD) ofst_lll = add_ofst(parent, ofst) if member_abi_t.is_dynamic(): # double dereference, according to ABI spec # TODO optimize special case: first dynamic item # offset is statically known. ofst_lll = add_ofst(parent, unwrap_location(ofst_lll)) return LLLnode.from_list( ofst_lll, typ=member_t, location=parent.location, encoding=parent.encoding, pos=pos, annotation=f"{parent}{ofst}", )
def test_lll_from_s_expression(get_contract_from_lll): code = """ (seq (deploy 0 (seq ; just return 32 byte of calldata back (calldatacopy 0 4 32) (return 0 32) stop ) 0)) """ abi = [{ "name": "test", "outputs": [{ "type": "int128", "name": "out" }], "inputs": [{ "type": "int128", "name": "a" }], "stateMutability": "nonpayable", "type": "function", "gas": 394, }] s_expressions = parse_s_exp(code) lll = LLLnode.from_list(s_expressions[0]) c = get_contract_from_lll(lll, abi=abi) assert c.test(-123456) == -123456
def mzero(dst, nbytes): # calldatacopy from past-the-end gives zero bytes. # cf. YP H.2 (ops section) with CALLDATACOPY spec. return LLLnode.from_list( # calldatacopy mempos calldatapos len ["calldatacopy", dst, "calldatasize", nbytes], annotation="mzero", )
def parse_UnaryOp(self): operand = Expr.parse_value_expr(self.expr.operand, self.context) if isinstance(self.expr.op, vy_ast.Not): if isinstance(operand.typ, BaseType) and operand.typ.typ == "bool": return LLLnode.from_list(["iszero", operand], typ="bool", pos=getpos(self.expr)) elif isinstance(self.expr.op, vy_ast.USub) and is_numeric_type( operand.typ): # Clamp on minimum integer value as we cannot negate that value # (all other integer values are fine) min_int_val = get_min_val_for_type(operand.typ.typ) return LLLnode.from_list( ["sub", 0, ["clampgt", operand, min_int_val]], typ=operand.typ, pos=getpos(self.expr), )
def generate_lll_for_external_function(code, sig, context, check_nonpayable): # TODO type hints: # def generate_lll_for_external_function( # code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context, check_nonpayable: bool, # ) -> LLLnode: """Return the LLL for an external function. Includes code to inspect the method_id, enter the function (nonpayable and reentrancy checks), handle kwargs and exit the function (clean up reentrancy storage variables) """ func_type = code._metadata["type"] pos = getpos(code) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) # generate handlers for base args and register the variable records handle_base_args = _register_function_args(context, sig) # generate handlers for kwargs and register the variable records kwarg_handlers = _generate_kwarg_handlers(context, sig, pos) # once optional args have been handled, # generate the main body of the function entrance = [["label", sig.external_function_base_entry_label]] entrance += handle_base_args if check_nonpayable and sig.mutability != "payable": # if the contract contains payable functions, but this is not one of them # add an assertion that the value of the call is zero entrance += [["assert", ["iszero", "callvalue"]]] entrance += nonreentrant_pre body = [parse_body(c, context) for c in code.body] exit = [["label", sig.exit_sequence_label]] + nonreentrant_post if sig.is_init_func: pass # init func has special exit sequence generated by module.py elif context.return_type is None: exit += [["stop"]] else: # ret_ofst and ret_len stack items passed by function body; consume using 'pass' exit += [["return", "pass", "pass"]] # the lll which comprises the main body of the function, # besides any kwarg handling func_common_lll = ["seq"] + entrance + body + exit if sig.is_default_func or sig.is_init_func: # default and init funcs have special entries generated by module.py ret = func_common_lll else: ret = kwarg_handlers # sneak the base code into the kwarg handler # TODO rethink this / make it clearer ret[-1][-1].append(func_common_lll) return LLLnode.from_list(ret, pos=getpos(code))
def make_setter(left, right, pos): check_assign(left, right) # Basic types if isinstance(left.typ, BaseType): enc = right.encoding # unwrap_location butchers encoding right = unwrap_location(right) # TODO rethink/streamline the clamp_basetype logic if _needs_clamp(right.typ, enc): right = clamp_basetype(right) op = store_op(left.location) return LLLnode.from_list([op, left, right], pos=pos) # Byte arrays elif isinstance(left.typ, ByteArrayLike): # TODO rethink/streamline the clamp_basetype logic if _needs_clamp(right.typ, right.encoding): with right.cache_when_complex("bs_ptr") as (b, right): copier = make_byte_array_copier(left, right, pos) ret = b.resolve(["seq", clamp_bytestring(right), copier]) else: ret = make_byte_array_copier(left, right, pos) return LLLnode.from_list(ret) elif isinstance(left.typ, DArrayType): # TODO should we enable this? # implicit conversion from sarray to darray # if isinstance(right.typ, SArrayType): # return _complex_make_setter(left, right, pos) # TODO rethink/streamline the clamp_basetype logic if _needs_clamp(right.typ, right.encoding): with right.cache_when_complex("arr_ptr") as (b, right): copier = _dynarray_make_setter(left, right, pos) ret = b.resolve(["seq", clamp_dyn_array(right), copier]) else: ret = _dynarray_make_setter(left, right, pos) return LLLnode.from_list(ret) # Arrays elif isinstance(left.typ, (SArrayType, TupleLike)): return _complex_make_setter(left, right, pos)
def handler_for(calldata_kwargs, default_kwargs): calldata_args = sig.base_args + calldata_kwargs # create a fake type so that get_element_ptr works calldata_args_t = TupleType(list(arg.typ for arg in calldata_args)) abi_sig = sig.abi_signature_for_kwargs(calldata_kwargs) method_id = _annotated_method_id(abi_sig) calldata_kwargs_ofst = LLLnode(4, location="calldata", typ=calldata_args_t, encoding=Encoding.ABI) # a sequence of statements to strictify kwargs into memory ret = ["seq"] # TODO optimize make_setter by using # TupleType(list(arg.typ for arg in calldata_kwargs + default_kwargs)) # (must ensure memory area is contiguous) n_base_args = len(sig.base_args) for i, arg_meta in enumerate(calldata_kwargs): k = n_base_args + i dst = context.lookup_var(arg_meta.name).pos lhs = LLLnode(dst, location="memory", typ=arg_meta.typ) rhs = get_element_ptr(calldata_kwargs_ofst, k, pos=None, array_bounds_check=False) ret.append(make_setter(lhs, rhs, pos)) for x in default_kwargs: dst = context.lookup_var(x.name).pos lhs = LLLnode(dst, location="memory", typ=x.typ) kw_ast_val = sig.default_values[x.name] # e.g. `3` in x: int = 3 rhs = Expr(kw_ast_val, context).lll_node ret.append(make_setter(lhs, rhs, pos)) ret.append(["goto", sig.external_function_base_entry_label]) ret = ["if", ["eq", "_calldata_method_id", method_id], ret] return ret
def eval_seq(lll_node): """Tries to find the "return" value of a `seq` statement, in order so that the value can be known without possibly evaluating side effects """ if lll_node.value in ("seq", "with") and len(lll_node.args) > 0: return eval_seq(lll_node.args[-1]) if isinstance(lll_node.value, int): return LLLnode.from_list(lll_node) return None
def parse_Tuple(self): tuple_elements = [ Expr(x, self.context).lll_node for x in self.expr.elements ] typ = TupleType([x.typ for x in tuple_elements], is_literal=True) multi_lll = LLLnode.from_list(["multi"] + tuple_elements, typ=typ, pos=getpos(self.expr)) return multi_lll
def lll_for_external_call(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import pos = getpos(stmt_expr) value, gas, skip_contract_check = _get_special_kwargs(stmt_expr, context) args_lll = [Expr(x, context).lll_node for x in stmt_expr.args] if isinstance(stmt_expr.func, vy_ast.Attribute) and isinstance( stmt_expr.func.value, vy_ast.Call): # e.g. `Foo(address).bar()` # sanity check assert len(stmt_expr.func.value.args) == 1 contract_name = stmt_expr.func.value.func.id contract_address = Expr.parse_value_expr(stmt_expr.func.value.args[0], context) elif (isinstance(stmt_expr.func.value, vy_ast.Attribute) and stmt_expr.func.value.attr in context.globals # TODO check for self? and hasattr(context.globals[stmt_expr.func.value.attr].typ, "name")): # e.g. `self.foo.bar()` # sanity check assert stmt_expr.func.value.value.id == "self", stmt_expr contract_name = context.globals[stmt_expr.func.value.attr].typ.name type_ = stmt_expr.func.value._metadata["type"] var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( type_.position.position, typ=var.typ, location="storage", pos=pos, annotation="self." + stmt_expr.func.value.attr, )) else: # TODO catch this during type checking raise StructureException("Unsupported operator.", stmt_expr) method_name = stmt_expr.func.attr contract_sig = context.sigs[contract_name][method_name] ret = _external_call_helper( contract_address, contract_sig, args_lll, context, pos, value=value, gas=gas, skip_contract_check=skip_contract_check, ) ret.annotation = stmt_expr.get("node_source_code") return ret