def hexstring(self): orignum = self.expr.value if len(orignum) == 42: if checksum_encode(orignum) != orignum: raise InvalidLiteral( "Address checksum mismatch. If you are sure this is the " f"right address, the correct checksummed form is: {checksum_encode(orignum)}", self.expr ) 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), ) else: raise InvalidLiteral( f"Cannot read 0x value with length {len(orignum)}. Expecting 42 (address " "incl 0x) or 66 (bytes32 incl 0x)", self.expr )
def variables(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. 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.context.constants.ast_is_constant(self.expr): return self.context.constants.get_constant(self.expr.id, self.context) else: raise VariableDeclarationException(f"Undeclared variable: {self.expr.id}", self.expr)
def unary_operations(self): operand = Expr.parse_value_expr(self.expr.operand, self.context) if isinstance(self.expr.op, sri_ast.Not): if isinstance(operand.typ, BaseType) and operand.typ.typ == 'bool': return LLLnode.from_list(["iszero", operand], typ='bool', pos=getpos(self.expr)) else: raise TypeMismatch( f"Only bool is supported for not operation, {operand.typ} supplied.", self.expr, ) elif isinstance(self.expr.op, sri_ast.USub): if not is_numeric_type(operand.typ): raise TypeMismatch( f"Unsupported type for negation: {operand.typ}", self.expr, ) # 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) ) else: raise StructureException("Only the 'not' or 'neg' unary operators are supported")
def gen_tuple_return(stmt, context, sub): # Is from a call expression. if sub.args and len(sub.args[0].args) > 0 and sub.args[0].args[0].value == 'call': # self-call to public. mem_pos = sub mem_size = get_size_of_type(sub.typ) * 32 return LLLnode.from_list(['return', mem_pos, mem_size], typ=sub.typ) elif (sub.annotation and 'Internal Call' in sub.annotation): mem_pos = sub.args[-1].value if sub.value == 'seq_unchecked' else sub.args[0].args[-1] mem_size = get_size_of_type(sub.typ) * 32 # Add zero padder if bytes are present in output. zero_padder = ['pass'] byte_arrays = [ (i, x) for i, x in enumerate(sub.typ.tuple_members()) if isinstance(x, ByteArrayLike) ] if byte_arrays: i, x = byte_arrays[-1] zero_padder = zero_pad(bytez_placeholder=[ 'add', mem_pos, ['mload', mem_pos + i * 32] ]) return LLLnode.from_list(['seq'] + [sub] + [zero_padder] + [ make_return_stmt(stmt, context, mem_pos, mem_size) ], typ=sub.typ, pos=getpos(stmt), valency=0) abi_typ = abi_type_of(context.return_type) # according to the ABI, return types are ALWAYS tuples even if # only one element is being returned. # https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector-and-argument-encoding # "and the return values v_1, ..., v_k of f are encoded as # # enc((v_1, ..., v_k)) # i.e. the values are combined into a tuple and encoded. # " # therefore, wrap it in a tuple if it's not already a tuple. # (big difference between returning `(bytes,)` and `bytes`. abi_typ = ensure_tuple(abi_typ) abi_bytes_needed = abi_typ.static_size() + abi_typ.dynamic_size_bound() dst, _ = context.memory_allocator.increase_memory(abi_bytes_needed) return_buffer = LLLnode( dst, location='memory', annotation='return_buffer', typ=context.return_type) check_assign(return_buffer, sub, pos=getpos(stmt)) encode_out = abi_encode(return_buffer, sub, pos=getpos(stmt), returns=True) load_return_len = ['mload', MemoryPositions.FREE_VAR_SPACE] os = ['seq', ['mstore', MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len)] return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0)
def unwrap_location(orig): if orig.location == 'memory': return LLLnode.from_list(['mload', orig], typ=orig.typ) elif orig.location == 'storage': return LLLnode.from_list(['sload', orig], typ=orig.typ) elif orig.location == 'calldata': return LLLnode.from_list(['calldataload', orig], typ=orig.typ) else: return orig
def integer(self): # Literal (mostly likely) becomes int128 if SizeLimits.in_bounds('int128', self.expr.n) or self.expr.n < 0: return LLLnode.from_list( self.expr.n, typ=BaseType('int128', 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 get_sig_statements(sig, pos): method_id_node = LLLnode.from_list(sig.method_id, pos=pos, annotation=f'{sig.sig}') if sig.private: sig_compare = 0 private_label = LLLnode.from_list(['label', f'priv_{sig.method_id}'], pos=pos, annotation=f'{sig.sig}') else: sig_compare = ['eq', ['mload', 0], method_id_node] private_label = ['pass'] return sig_compare, private_label
def boolean_operations(self): # Iterate through values for value in self.expr.values: # Check for calls at assignment if self.context.in_assignment and isinstance(value, sri_ast.Call): raise StructureException( "Boolean operations with calls may not be performed on assignment", self.expr, ) # Check for boolean operations with non-boolean inputs _expr = Expr.parse_value_expr(value, self.context) if not is_base_type(_expr.typ, 'bool'): raise TypeMismatch( "Boolean operations can only be between booleans!", self.expr, ) # TODO: Handle special case of literals and simplify at compile time # Check for valid ops if isinstance(self.expr.op, sri_ast.And): op = 'and' elif isinstance(self.expr.op, sri_ast.Or): op = 'or' else: raise Exception("Unsupported bool op: " + self.expr.op) # Handle different numbers of inputs count = len(self.expr.values) if count < 2: raise StructureException("Expected at least two arguments for a bool op", self.expr) elif count == 2: left = Expr.parse_value_expr(self.expr.values[0], self.context) right = Expr.parse_value_expr(self.expr.values[1], self.context) return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr)) else: left = Expr.parse_value_expr(self.expr.values[0], self.context) right = Expr.parse_value_expr(self.expr.values[1], self.context) p = ['seq', [op, left, right]] values = self.expr.values[2:] while len(values) > 0: value = Expr.parse_value_expr(values[0], self.context) p = [op, value, p] values = values[1:] return LLLnode.from_list(p, typ='bool', pos=getpos(self.expr))
def lazy_abi_decode(typ, src, pos=None): if isinstance(typ, (ListType, TupleLike)): if isinstance(typ, TupleLike): ts = typ.tuple_members() else: ts = [typ.subtyp for _ in range(typ.count)] ofst = 0 os = [] for t in ts: child_abi_t = abi_type_of(t) loc = _add_ofst(src, ofst) if child_abi_t.is_dynamic(): # load the offset word, which is the # (location-independent) offset from the start of the # src buffer. dyn_ofst = unwrap_location(ofst) loc = _add_ofst(src, dyn_ofst) os.append(lazy_abi_decode(t, loc, pos)) ofst += child_abi_t.embedded_static_size() return LLLnode.from_list(['multi'] + os, typ=typ, pos=pos) elif isinstance(typ, (BaseType, ByteArrayLike)): return unwrap_location(src) else: raise CompilerPanic(f'unknown type for lazy_abi_decode {typ}')
def struct_literals(expr, name, context): member_subs = {} member_typs = {} for key, value in zip(expr.keys, expr.values): if not isinstance(key, sri_ast.Name): raise TypeMismatch( f"Invalid member variable for struct: {getattr(key, 'id', '')}", key, ) check_valid_varname( key.id, context.structs, context.constants, "Invalid member variable for struct", ) if key.id in member_subs: raise TypeMismatch("Member variable duplicated: " + key.id, key) sub = Expr(value, context).lll_node member_subs[key.id] = sub member_typs[key.id] = sub.typ return LLLnode.from_list( ["multi"] + [member_subs[key] for key in member_subs.keys()], typ=StructType(member_typs, name, is_literal=True), pos=getpos(expr), )
def list_literals(self): if not len(self.expr.elts): raise StructureException("List must have elements", self.expr) def get_out_type(lll_node): if isinstance(lll_node, ListType): return get_out_type(lll_node.subtype) return lll_node.typ o = [] previous_type = None out_type = None for elt in self.expr.elts: current_lll_node = Expr(elt, self.context).lll_node if not out_type: out_type = current_lll_node.typ current_type = get_out_type(current_lll_node) if len(o) > 0 and previous_type != current_type: raise TypeMismatch("Lists may only contain one type", self.expr) else: o.append(current_lll_node) previous_type = current_type return LLLnode.from_list( ["multi"] + o, typ=ListType(out_type, len(o)), pos=getpos(self.expr), )
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 tuple_literals(self): if not len(self.expr.elts): raise StructureException("Tuple must have elements", self.expr) o = [] for elt in self.expr.elts: o.append(Expr(elt, self.context).lll_node) typ = TupleType([x.typ for x in o], is_literal=True) return LLLnode.from_list(["multi"] + o, typ=typ, pos=getpos(self.expr))
def base_type_conversion(orig, frm, to, pos, in_function_call=False): orig = unwrap_location(orig) # do the base type check so we can use BaseType attributes if not isinstance(frm, BaseType) or not isinstance(to, BaseType): raise TypeMismatch( f"Base type conversion from or to non-base type: {frm} {to}", pos) if getattr(frm, 'is_literal', False): if frm.typ in ('int128', 'uint256'): if not SizeLimits.in_bounds(frm.typ, orig.value): raise InvalidLiteral(f"Number out of range: {orig.value}", pos) if to.typ in ('int128', 'uint256'): if not SizeLimits.in_bounds(to.typ, orig.value): raise InvalidLiteral(f"Number out of range: {orig.value}", pos) is_decimal_int128_conversion = frm.typ == 'int128' and to.typ == 'decimal' is_same_type = frm.typ == to.typ is_literal_conversion = frm.is_literal and (frm.typ, to.typ) == ('int128', 'uint256') is_address_conversion = isinstance(frm, ContractType) and to.typ == 'address' if not (is_same_type or is_literal_conversion or is_address_conversion or is_decimal_int128_conversion): raise TypeMismatch( f"Typecasting from base type {frm} to {to} unavailable", pos) # handle None value inserted by `empty()` if orig.value is None: return LLLnode.from_list(0, typ=to) if is_decimal_int128_conversion: return LLLnode.from_list( ['mul', orig, DECIMAL_DIVISOR], typ=BaseType('decimal'), ) return LLLnode(orig.value, orig.args, typ=to, add_gas_estimate=orig.add_gas_estimate)
def constants(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), ) elif self.expr.value is None: # block None raise InvalidLiteral( 'None is not allowed in srilang' '(use a default value or built-in `empty()`') else: raise Exception(f"Unknown name constant: {self.expr.value.value}")
def byte_array_to_num( arg, expr, out_type, offset=32, ): if arg.location == "memory": lengetter = LLLnode.from_list(['mload', '_sub'], typ=BaseType('int128')) first_el_getter = LLLnode.from_list(['mload', ['add', 32, '_sub']], typ=BaseType('int128')) elif arg.location == "storage": lengetter = LLLnode.from_list(['sload', ['sha3_32', '_sub']], typ=BaseType('int128')) first_el_getter = LLLnode.from_list( ['sload', ['add', 1, ['sha3_32', '_sub']]], typ=BaseType('int128')) if out_type == 'int128': result = [ 'clamp', ['mload', MemoryPositions.MINNUM], ['div', '_el1', ['exp', 256, ['sub', 32, '_len']]], ['mload', MemoryPositions.MAXNUM] ] elif out_type == 'uint256': result = ['div', '_el1', ['exp', 256, ['sub', offset, '_len']]] return LLLnode.from_list([ 'with', '_sub', arg, [ 'with', '_el1', first_el_getter, [ 'with', '_len', ['clamp', 0, lengetter, 32], result, ] ] ], typ=BaseType(out_type), annotation=f'bytearray to number ({out_type})')
def keccak256_helper(expr, args, kwargs, context): sub = args[0] # Can hash literals if isinstance(sub, bytes): return LLLnode.from_list(bytes_to_int(keccak256(sub)), typ=BaseType('bytes32'), pos=getpos(expr)) # Can hash bytes32 objects if is_base_type(sub.typ, 'bytes32'): return LLLnode.from_list( [ 'seq', ['mstore', MemoryPositions.FREE_VAR_SPACE, sub], ['sha3', MemoryPositions.FREE_VAR_SPACE, 32] ], typ=BaseType('bytes32'), pos=getpos(expr), ) # Copy the data to an in-memory array if sub.location == "memory": # If we are hashing a value in memory, no need to copy it, just hash in-place return LLLnode.from_list( [ 'with', '_sub', sub, ['sha3', ['add', '_sub', 32], ['mload', '_sub']] ], typ=BaseType('bytes32'), pos=getpos(expr), ) elif sub.location == "storage": lengetter = LLLnode.from_list(['sload', ['sha3_32', '_sub']], typ=BaseType('int128')) else: # This should never happen, but just left here for future compiler-writers. raise Exception( f"Unsupported location: {sub.location}") # pragma: no test placeholder = context.new_placeholder(sub.typ) placeholder_node = LLLnode.from_list(placeholder, typ=sub.typ, location='memory') copier = make_byte_array_copier( placeholder_node, LLLnode.from_list('_sub', typ=sub.typ, location=sub.location), ) return LLLnode.from_list([ 'with', '_sub', sub, ['seq', copier, ['sha3', ['add', placeholder, 32], lengetter]], ], typ=BaseType('bytes32'), pos=getpos(expr))
def decimal(self): numstring, num, den = get_number_as_fraction(self.expr, self.context) # if not SizeLimits.in_bounds('decimal', num // den): # if not SizeLimits.MINDECIMAL * den <= num <= SizeLimits.MAXDECIMAL * den: if not (SizeLimits.MINNUM * den <= num <= SizeLimits.MAXNUM * den): raise InvalidLiteral("Number out of range: " + numstring, self.expr) if DECIMAL_DIVISOR % den: raise InvalidLiteral( "Type 'decimal' has maximum 10 decimal places", self.expr ) return LLLnode.from_list( num * DECIMAL_DIVISOR // den, typ=BaseType('decimal', is_literal=True), pos=getpos(self.expr), )
def o_list(lll_node, pos=None): lll_t = lll_node.typ if isinstance(lll_t, (TupleLike, ListType)): if lll_node.value == 'multi': # is literal ret = lll_node.args else: ks = lll_t.tuple_keys() if isinstance(lll_t, TupleLike) else \ [LLLnode.from_list(i, 'uint256') for i in range(lll_t.count)] ret = [ add_variable_offset(lll_node, k, pos, array_bounds_check=False) for k in ks ] return ret else: return [lll_node]
def zero_pad(bytez_placeholder): len_ = ['mload', bytez_placeholder] dst = ['add', ['add', bytez_placeholder, 32], 'len'] # the runtime length of the data rounded up to nearest 32 # from spec: # the actual value of X as a byte sequence, # followed by the *minimum* number of zero-bytes # such that len(enc(X)) is a multiple of 32. num_zero_bytes = ['sub', ['ceil32', 'len'], 'len'] return LLLnode.from_list( [ 'with', 'len', len_, ['with', 'dst', dst, mzero('dst', num_zero_bytes)] ], annotation="Zero pad", )
def _make_bytelike(self, btype, bytez, bytez_length): placeholder = self.context.new_placeholder(btype) seq = [] seq.append(['mstore', placeholder, bytez_length]) for i in range(0, len(bytez), 32): seq.append([ 'mstore', ['add', placeholder, i + 32], bytes_to_int((bytez + b'\x00' * 31)[i: i + 32]) ]) return LLLnode.from_list( ['seq'] + seq + [placeholder], typ=btype, location='memory', pos=getpos(self.expr), annotation=f'Create {btype}: {bytez}', )
def parse_other_functions(o, otherfuncs, sigs, external_contracts, origcode, global_ctx, default_function): sub = ['seq', func_init_lll()] add_gas = func_init_lll().gas for _def in otherfuncs: sub.append( parse_function(_def, { **{ 'self': sigs }, **external_contracts }, origcode, global_ctx)) sub[-1].total_gas += add_gas add_gas += 30 for sig in sig_utils.generate_default_arg_sigs(_def, external_contracts, global_ctx): sig.gas = sub[-1].total_gas sigs[sig.sig] = sig # Add fallback function if default_function: default_func = parse_function( default_function[0], { **{ 'self': sigs }, **external_contracts }, origcode, global_ctx, ) fallback = default_func else: fallback = LLLnode.from_list(['revert', 0, 0], typ=None, annotation='Default function') sub.append(['seq_unchecked', ['label', 'fallback'], fallback]) o.append(['return', 0, ['lll', sub, 0]]) return o, sub
def parse_private_function(code: ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a private function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :return: full sig compare & function body """ validate_private_function(code, sig) # Get nonreentrant lock nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock( sig, context.global_ctx) # Create callback_ptr, this stores a destination in the bytecode for a private # function to jump to after a function has executed. clampers: List[LLLnode] = [] # Allocate variable space. context.memory_allocator.increase_memory(sig.max_copy_size) _post_callback_ptr = f"{sig.name}_{sig.method_id}_post_callback_ptr" context.callback_ptr = context.new_placeholder(typ=BaseType('uint256')) clampers.append( LLLnode.from_list( ['mstore', context.callback_ptr, 'pass'], annotation='pop callback pointer', )) if sig.total_default_args > 0: clampers.append(LLLnode.from_list(['label', _post_callback_ptr])) # private functions without return types need to jump back to # the calling function, as there is no return statement to handle the # jump. if sig.output_type is None: stop_func = [['jump', ['mload', context.callback_ptr]]] else: stop_func = [['stop']] # Generate copiers if len(sig.base_args) == 0: copier = ['pass'] clampers.append(LLLnode.from_list(copier)) elif sig.total_default_args == 0: copier = get_private_arg_copier( total_size=sig.base_copy_size, memory_dest=MemoryPositions.RESERVED_MEMORY) clampers.append(LLLnode.from_list(copier)) # Fill variable positions for arg in sig.args: if isinstance(arg.typ, ByteArrayLike): mem_pos, _ = context.memory_allocator.increase_memory( 32 * get_size_of_type(arg.typ)) context.vars[arg.name] = VariableRecord(arg.name, mem_pos, arg.typ, False) else: context.vars[arg.name] = VariableRecord( arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False, ) # Private function copiers. No clamping for private functions. dyn_variable_names = [ a.name for a in sig.base_args if isinstance(a.typ, ByteArrayLike) ] if dyn_variable_names: i_placeholder = context.new_placeholder(typ=BaseType('uint256')) unpackers: List[Any] = [] for idx, var_name in enumerate(dyn_variable_names): var = context.vars[var_name] ident = f"_load_args_{sig.method_id}_dynarg{idx}" o = make_unpacker(ident=ident, i_placeholder=i_placeholder, begin_pos=var.pos) unpackers.append(o) if not unpackers: unpackers = ['pass'] # 0 added to complete full overarching 'seq' statement, see private_label. unpackers.append(0) clampers.append( LLLnode.from_list( ['seq_unchecked'] + unpackers, typ=None, annotation='dynamic unpacker', pos=getpos(code), )) # Function has default arguments. if sig.total_default_args > 0: # Function with default parameters. default_sigs = sig_utils.generate_default_arg_sigs( code, context.sigs, context.global_ctx) sig_chain: List[Any] = ['seq'] for default_sig in default_sigs: sig_compare, private_label = get_sig_statements( default_sig, getpos(code)) # Populate unset default variables set_defaults = [] for arg_name in get_default_names_to_set(sig, default_sig): value = Expr(sig.default_values[arg_name], context).lll_node var = context.vars[arg_name] left = LLLnode.from_list(var.pos, typ=var.typ, location='memory', pos=getpos(code), mutable=var.mutable) set_defaults.append( make_setter(left, value, 'memory', pos=getpos(code))) current_sig_arg_names = [x.name for x in default_sig.args] # Load all variables in default section, if private, # because the stack is a linear pipe. copier_arg_count = len(default_sig.args) copier_arg_names = current_sig_arg_names # Order copier_arg_names, this is very important. copier_arg_names = [ x.name for x in default_sig.args if x.name in copier_arg_names ] # Variables to be populated from calldata/stack. default_copiers: List[Any] = [] if copier_arg_count > 0: # Get map of variables in calldata, with thier offsets offset = 4 calldata_offset_map = {} for arg in default_sig.args: calldata_offset_map[arg.name] = offset offset += (32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32) # Copy set default parameters from calldata dynamics = [] for arg_name in copier_arg_names: var = context.vars[arg_name] if isinstance(var.typ, ByteArrayLike): _size = 32 dynamics.append(var.pos) else: _size = var.size * 32 default_copiers.append( get_private_arg_copier( memory_dest=var.pos, total_size=_size, )) # Unpack byte array if necessary. if dynamics: i_placeholder = context.new_placeholder( typ=BaseType('uint256')) for idx, var_pos in enumerate(dynamics): ident = f'unpack_default_sig_dyn_{default_sig.method_id}_arg{idx}' default_copiers.append( make_unpacker( ident=ident, i_placeholder=i_placeholder, begin_pos=var_pos, )) default_copiers.append(0) # for over arching seq, POP sig_chain.append([ 'if', sig_compare, [ 'seq', private_label, LLLnode.from_list([ 'mstore', context.callback_ptr, 'pass', ], annotation='pop callback pointer', pos=getpos(code)), ['seq'] + set_defaults if set_defaults else ['pass'], ['seq_unchecked'] + default_copiers if default_copiers else ['pass'], ['goto', _post_callback_ptr] ] ]) # With private functions all variable loading occurs in the default # function sub routine. _clampers = [['label', _post_callback_ptr]] # Function with default parameters. o = LLLnode.from_list( [ 'seq', sig_chain, [ 'if', 0, # can only be jumped into [ 'seq', ['seq'] + nonreentrant_pre + _clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + stop_func ], ], ], typ=None, pos=getpos(code)) else: # Function without default parameters. sig_compare, private_label = get_sig_statements(sig, getpos(code)) o = LLLnode.from_list([ 'if', sig_compare, ['seq'] + [private_label] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + stop_func ], typ=None, pos=getpos(code)) return o return o
def abi_encode(dst, lll_node, pos=None, bufsz=None, returns=False): parent_abi_t = abi_type_of(lll_node.typ) size_bound = parent_abi_t.static_size() + parent_abi_t.dynamic_size_bound() if bufsz is not None and bufsz < 32 * size_bound: raise CompilerPanic('buffer provided to abi_encode not large enough') lll_ret = ['seq'] dyn_ofst = 'dyn_ofst' # current offset in the dynamic section dst_begin = 'dst' # pointer to beginning of buffer dst_loc = 'dst_loc' # pointer to write location in static section os = o_list(lll_node, pos=pos) for i, o in enumerate(os): abi_t = abi_type_of(o.typ) if parent_abi_t.is_tuple(): if abi_t.is_dynamic(): lll_ret.append(['mstore', dst_loc, dyn_ofst]) # recurse child_dst = ['add', dst_begin, dyn_ofst] child = abi_encode(child_dst, o, pos=pos, returns=True) # increment dyn ofst for the return # (optimization note: # if non-returning and this is the last dyn member in # the tuple, this set can be elided.) lll_ret.append(['set', dyn_ofst, ['add', dyn_ofst, child]]) else: # recurse lll_ret.append(abi_encode(dst_loc, o, pos=pos, returns=False)) elif isinstance(o.typ, BaseType): d = LLLnode(dst_loc, typ=o.typ, location='memory') lll_ret.append(make_setter(d, o, location=d.location, pos=pos)) elif isinstance(o.typ, ByteArrayLike): d = LLLnode.from_list(dst_loc, typ=o.typ, location='memory') lll_ret.append([ 'seq', make_setter(d, o, location=d.location, pos=pos), zero_pad(d) ]) else: raise CompilerPanic(f'unreachable type: {o.typ}') if i + 1 == len(os): pass # optimize out the last increment to dst_loc else: # note: always false for non-tuple types sz = abi_t.embedded_static_size() lll_ret.append(['set', dst_loc, ['add', dst_loc, sz]]) # declare LLL variables. if returns: if not parent_abi_t.is_dynamic(): lll_ret.append(parent_abi_t.embedded_static_size()) elif parent_abi_t.is_tuple(): lll_ret.append('dyn_ofst') elif isinstance(lll_node.typ, ByteArrayLike): # for abi purposes, return zero-padded length calc_len = ['ceil32', ['add', 32, ['mload', dst_loc]]] lll_ret.append(calc_len) else: raise CompilerPanic('unknown type {lll_node.typ}') if not (parent_abi_t.is_dynamic() and parent_abi_t.is_tuple()): pass # optimize out dyn_ofst allocation if we don't need it else: dyn_section_start = parent_abi_t.static_size() lll_ret = ['with', 'dyn_ofst', dyn_section_start, lll_ret] lll_ret = ['with', dst_begin, dst, ['with', dst_loc, dst_begin, lll_ret]] return LLLnode.from_list(lll_ret)
def make_byte_slice_copier(destination, source, length, max_length, pos=None): # Special case: memory to memory if source.location == "memory" and destination.location == "memory": return LLLnode.from_list( [ 'with', '_l', max_length, [ 'pop', ['call', ['gas'], 4, 0, source, '_l', destination, '_l'] ] ], typ=None, annotation=f'copy byte slice dest: {str(destination)}') # special case: rhs is zero if source.value is None: if destination.location == 'memory': return mzero(destination, max_length) else: loader = 0 # Copy over data elif source.location == "memory": loader = [ 'mload', [ 'add', '_pos', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ] ] elif source.location == "storage": loader = [ 'sload', ['add', '_pos', ['mload', MemoryPositions.FREE_LOOP_INDEX]] ] else: raise CompilerPanic(f'Unsupported location: {source.location}') # Where to paste it? if destination.location == "memory": setter = [ 'mstore', [ 'add', '_opos', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], loader ] elif destination.location == "storage": setter = [ 'sstore', ['add', '_opos', ['mload', MemoryPositions.FREE_LOOP_INDEX]], loader ] else: raise CompilerPanic(f"Unsupported location: {destination.location}") # Check to see if we hit the length checker = [ 'if', [ 'gt', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]], '_actual_len' ], 'break' ] # Make a loop to do the copying ipos = 0 if source.value is None else source o = [ 'with', '_pos', ipos, [ 'with', '_opos', destination, [ 'with', '_actual_len', length, [ 'repeat', MemoryPositions.FREE_LOOP_INDEX, 0, (max_length + 31) // 32, ['seq', checker, setter] ] ] ] ] return LLLnode.from_list( o, typ=None, annotation=f'copy byte slice src: {source} dst: {destination}', pos=pos, )
def get_length(arg): if arg.location == "memory": return LLLnode.from_list(['mload', arg], typ=BaseType('int128')) elif arg.location == "storage": return LLLnode.from_list(['sload', ['sha3_32', arg]], typ=BaseType('int128'))
def add_variable_offset(parent, key, pos, array_bounds_check=True): typ, location = parent.typ, parent.location if isinstance(typ, TupleLike): if isinstance(typ, StructType): if not isinstance(key, str): raise TypeMismatch( f"Expecting a member variable access; cannot access element {key}", pos) if key not in typ.members: raise TypeMismatch( f"Object does not have member variable {key}", pos) subtype = typ.members[key] attrs = list(typ.tuple_keys()) if key not in attrs: raise TypeMismatch( f"Member {key} not found. Only the following available: " + " ".join(attrs), pos) index = attrs.index(key) annotation = key else: if not isinstance(key, int): raise TypeMismatch( f"Expecting a static index; cannot access element {key}", pos) attrs = list(range(len(typ.members))) index = key annotation = None if location == 'storage': return LLLnode.from_list( [ 'add', ['sha3_32', parent], LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location == 'storage_prehashed': return LLLnode.from_list( [ 'add', parent, LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location in ('calldata', 'memory'): offset = 0 for i in range(index): offset += 32 * get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list(['add', offset, parent], typ=typ.members[key], location=location, annotation=annotation) else: raise TypeMismatch("Not expecting a member variable access", pos) elif isinstance(typ, MappingType): if isinstance(key.typ, ByteArrayLike): if not isinstance(typ.keytype, ByteArrayLike) or ( typ.keytype.maxlen < key.typ.maxlen): raise TypeMismatch( "Mapping keys of bytes cannot be cast, use exact same bytes type of: " f"{str(typ.keytype)}", pos, ) subtype = typ.valuetype if len(key.args[0].args) >= 3: # handle bytes literal. sub = LLLnode.from_list([ 'seq', key, [ 'sha3', ['add', key.args[0].args[-1], 32], ['mload', key.args[0].args[-1]] ] ]) else: sub = LLLnode.from_list([ 'sha3', ['add', key.args[0].value, 32], ['mload', key.args[0].value] ]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos) if location == 'storage': return LLLnode.from_list(['sha3_64', parent, sub], typ=subtype, location='storage') elif location in ('memory', 'calldata'): raise TypeMismatch( "Can only have fixed-side arrays in memory, not mappings", pos) elif isinstance(typ, ListType): subtype = typ.subtype k = unwrap_location(key) if not is_base_type(key.typ, ('int128', 'uint256')): raise TypeMismatch(f'Invalid type for array index: {key.typ}', pos) if not array_bounds_check: sub = k elif key.typ.is_literal: # note: BaseType always has is_literal attr # perform the check at compile time and elide the runtime check. if key.value < 0 or key.value >= typ.count: raise ArrayIndexException( 'Array index determined to be out of bounds. ' f'Index is {key.value} but array size is {typ.count}', pos) sub = k else: # this works, even for int128. for int128, since two's-complement # is used, if the index is negative, (unsigned) LT will interpret # it as a very large number, larger than any practical value for # an array index, and the clamp will throw an error. sub = ['uclamplt', k, typ.count] if location == 'storage': return LLLnode.from_list(['add', ['sha3_32', parent], sub], typ=subtype, location='storage', pos=pos) elif location == 'storage_prehashed': return LLLnode.from_list(['add', parent, sub], typ=subtype, location='storage', pos=pos) elif location in ('calldata', 'memory'): offset = 32 * get_size_of_type(subtype) return LLLnode.from_list(['add', ['mul', offset, sub], parent], typ=subtype, location=location, pos=pos) else: raise TypeMismatch("Not expecting an array access ", pos) else: raise TypeMismatch( f"Cannot access the child of a constant variable! {typ}", pos)
def pack_arguments(signature, args, context, stmt_expr, return_placeholder=True): pos = getpos(stmt_expr) placeholder_typ = ByteArrayType( maxlen=sum([get_size_of_type(arg.typ) for arg in signature.args]) * 32 + 32) placeholder = context.new_placeholder(placeholder_typ) setters = [['mstore', placeholder, signature.method_id]] needpos = False staticarray_offset = 0 expected_arg_count = len(signature.args) actual_arg_count = len(args) if actual_arg_count != expected_arg_count: raise StructureException( f"Wrong number of args for: {signature.name} " f"({actual_arg_count} args given, expected {expected_arg_count}", stmt_expr) for i, (arg, typ) in enumerate(zip(args, [arg.typ for arg in signature.args])): if isinstance(typ, BaseType): setters.append( make_setter(LLLnode.from_list( placeholder + staticarray_offset + 32 + i * 32, typ=typ, ), arg, 'memory', pos=pos, in_function_call=True)) elif isinstance(typ, ByteArrayLike): setters.append([ 'mstore', placeholder + staticarray_offset + 32 + i * 32, '_poz' ]) arg_copy = LLLnode.from_list('_s', typ=arg.typ, location=arg.location) target = LLLnode.from_list( ['add', placeholder + 32, '_poz'], typ=typ, location='memory', ) setters.append([ 'with', '_s', arg, [ 'seq', make_byte_array_copier(target, arg_copy, pos), [ 'set', '_poz', [ 'add', 32, ['ceil32', ['add', '_poz', get_length(arg_copy)]] ] ], ], ]) needpos = True elif isinstance(typ, (StructType, ListType)): if has_dynamic_data(typ): raise TypeMismatch("Cannot pack bytearray in struct", stmt_expr) target = LLLnode.from_list( [placeholder + 32 + staticarray_offset + i * 32], typ=typ, location='memory', ) setters.append(make_setter(target, arg, 'memory', pos=pos)) if (isinstance(typ, ListType)): count = typ.count else: count = len(typ.tuple_items()) staticarray_offset += 32 * (count - 1) else: raise TypeMismatch(f"Cannot pack argument of type {typ}", stmt_expr) # For private call usage, doesn't use a returner. returner = [[placeholder + 28]] if return_placeholder else [] if needpos: return (LLLnode.from_list([ 'with', '_poz', len(args) * 32 + staticarray_offset, ['seq'] + setters + returner ], typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32) else: return (LLLnode.from_list(['seq'] + setters + returner, typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32)
def make_setter(left, right, location, pos, in_function_call=False): # Basic types if isinstance(left.typ, BaseType): right = base_type_conversion( right, right.typ, left.typ, pos, in_function_call=in_function_call, ) if location == 'storage': return LLLnode.from_list(['sstore', left, right], typ=None) elif location == 'memory': return LLLnode.from_list(['mstore', left, right], typ=None) # Byte arrays elif isinstance(left.typ, ByteArrayLike): return make_byte_array_copier(left, right, pos) # Can't copy mappings elif isinstance(left.typ, MappingType): raise TypeMismatch( "Cannot copy mappings; can only copy individual elements", pos) # Arrays elif isinstance(left.typ, ListType): # Cannot do something like [a, b, c] = [1, 2, 3] if left.value == "multi": raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, ListType): raise TypeMismatch( f"Setter type mismatch: left side is {left.typ}, right side is {right.typ}", pos) if right.typ.count != left.typ.count: raise TypeMismatch("Mismatched number of elements", pos) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" # If the right side is a literal if right.value == "multi": subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), right.args[i], location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) elif right.value is None: if right.typ != left.typ: raise TypeMismatch( f"left side is {left.typ}, right side is {right.typ}", pos) if left.location == 'memory': return mzero(left, 32 * get_size_of_type(left.typ)) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), LLLnode.from_list(None, typ=right.typ.subtype), location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a variable else: right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), add_variable_offset( right_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), location, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None) # Structs elif isinstance(left.typ, TupleLike): if left.value == "multi" and isinstance(left.typ, StructType): raise Exception("Target of set statement must be a single item") if right.value is not None: if not isinstance(right.typ, left.typ.__class__): raise TypeMismatch( f"Setter type mismatch: left side is {left.typ}, right side is {right.typ}", pos, ) if isinstance(left.typ, StructType): for k in left.typ.members: if k not in right.typ.members: raise TypeMismatch( f"Keys don't match for structs, missing {k}", pos, ) for k in right.typ.members: if k not in left.typ.members: raise TypeMismatch( f"Keys don't match for structs, extra {k}", pos, ) if left.typ.name != right.typ.name: raise TypeMismatch(f"Expected {left.typ}, got {right.typ}", pos) else: if len(left.typ.members) != len(right.typ.members): raise TypeMismatch( "Tuple lengths don't match, " f"{len(left.typ.members)} vs {len(right.typ.members)}", pos, ) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" keyz = left.typ.tuple_keys() # If the left side is a literal if left.value == 'multi': locations = [arg.location for arg in left.args] else: locations = [location for _ in keyz] # If the right side is a literal if right.value == "multi": if len(right.args) != len(keyz): raise TypeMismatch("Mismatched number of elements", pos) # get the RHS arguments into a dict because # they are not guaranteed to be in the same order # the LHS keys. right_args = dict(zip(right.typ.tuple_keys(), right.args)) subs = [] for (key, loc) in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, key, pos=pos), right_args[key], loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif right.value is None: if left.typ != right.typ: raise TypeMismatch( f"left side is {left.typ}, right side is {right.typ}", pos) if left.location == 'memory': return mzero(left, 32 * get_size_of_type(left.typ)) subs = [] for key, loc in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, key, pos=pos), LLLnode.from_list(None, typ=right.typ.members[key]), loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If tuple assign. elif isinstance(left.typ, TupleType) and isinstance( right.typ, TupleType): subs = [] static_offset_counter = 0 zipped_components = zip(left.args, right.typ.members, locations) for var_arg in left.args: if var_arg.location == 'calldata': raise ConstancyViolation( f"Cannot modify function argument: {var_arg.annotation}", pos) for left_arg, right_arg, loc in zipped_components: if isinstance(right_arg, ByteArrayLike): RType = ByteArrayType if isinstance( right_arg, ByteArrayType) else StringType offset = LLLnode.from_list([ 'add', '_R', ['mload', ['add', '_R', static_offset_counter]] ], typ=RType(right_arg.maxlen), location='memory', pos=pos) static_offset_counter += 32 else: offset = LLLnode.from_list( ['mload', ['add', '_R', static_offset_counter]], typ=right_arg.typ, pos=pos, ) static_offset_counter += get_size_of_type(right_arg) * 32 subs.append(make_setter(left_arg, offset, loc, pos=pos)) return LLLnode.from_list( ['with', '_R', right, ['seq'] + subs], typ=None, annotation='Tuple assignment', ) # If the right side is a variable else: subs = [] right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) for typ, loc in zip(keyz, locations): subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), add_variable_offset(right_token, typ, pos=pos), loc, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None, ) else: raise Exception("Invalid type for setters")
def make_byte_array_copier(destination, source, pos=None): if not isinstance(source.typ, ByteArrayLike): btype = 'byte array' if isinstance(destination.typ, ByteArrayType) else 'string' raise TypeMismatch(f"Can only set a {btype} to another {btype}", pos) if isinstance( source.typ, ByteArrayLike) and source.typ.maxlen > destination.typ.maxlen: raise TypeMismatch( f"Cannot cast from greater max-length {source.typ.maxlen} to shorter " f"max-length {destination.typ.maxlen}") # stricter check for zeroing a byte array. if isinstance(source.typ, ByteArrayLike): if source.value is None and source.typ.maxlen != destination.typ.maxlen: raise TypeMismatch( f"Bad type for clearing bytes: expected {destination.typ}" f" but got {source.typ}") # Special case: memory to memory if source.location == "memory" and destination.location == "memory": gas_calculation = GAS_IDENTITY + GAS_IDENTITYWORD * ( ceil32(source.typ.maxlen) // 32) o = LLLnode.from_list( [ 'with', '_source', source, [ 'with', '_sz', ['add', 32, ['mload', '_source']], [ 'assert', [ 'call', ['gas'], 4, 0, '_source', '_sz', destination, '_sz' ] ] ] ], # noqa: E501 typ=None, add_gas_estimate=gas_calculation, annotation='Memory copy') return o if source.value is None: pos_node = source else: pos_node = LLLnode.from_list('_pos', typ=source.typ, location=source.location) # Get the length if source.value is None: length = 1 elif source.location == "memory": length = ['add', ['mload', '_pos'], 32] elif source.location == "storage": length = ['add', ['sload', '_pos'], 32] pos_node = LLLnode.from_list( ['sha3_32', pos_node], typ=source.typ, location=source.location, ) else: raise CompilerPanic(f"Unsupported location: {source.location}") if destination.location == "storage": destination = LLLnode.from_list( ['sha3_32', destination], typ=destination.typ, location=destination.location, ) # Maximum theoretical length max_length = 32 if source.value is None else source.typ.maxlen + 32 return LLLnode.from_list([ 'with', '_pos', 0 if source.value is None else source, make_byte_slice_copier( destination, pos_node, length, max_length, pos=pos) ], typ=None)