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 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 call(self): from srilang.functions import ( DISPATCH_TABLE, ) if isinstance(self.expr.func, sri_ast.Name): function_name = self.expr.func.id if function_name in DISPATCH_TABLE: return DISPATCH_TABLE[function_name].build_LLL(self.expr, self.context) # Struct constructors do not need `self` prefix. elif function_name in self.context.structs: args = self.expr.args if len(args) != 1: raise StructureException( "Struct constructor is called with one argument only", self.expr, ) arg = args[0] if not isinstance(arg, sri_ast.Dict): raise TypeMismatch( "Struct can only be constructed with a dict", self.expr, ) return Expr.struct_literals(arg, function_name, self.context) # Contract assignment. Bar(<address>). elif function_name in self.context.sigs: ret, arg_lll = self._is_valid_contract_assign() if ret is True: arg_lll.typ = ContractType(function_name) # Cast to Correct contract type. return arg_lll else: raise TypeMismatch( "ContractType definition expects one address argument.", self.expr, ) else: err_msg = f"Not a top-level function: {function_name}" if function_name in [x.split('(')[0] for x, _ in self.context.sigs['self'].items()]: err_msg += f". Did you mean self.{function_name}?" raise StructureException(err_msg, self.expr) elif isinstance(self.expr.func, sri_ast.Attribute) and isinstance(self.expr.func.value, sri_ast.Name) and self.expr.func.value.id == "self": # noqa: E501 return self_call.make_call(self.expr, self.context) else: return external_call.make_external_call(self.expr, self.context)
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 get_min_val_for_type(typ: str) -> int: key = 'MIN_' + typ.upper() try: min_val, _ = BUILTIN_CONSTANTS[key] except KeyError as e: raise TypeMismatch(f"Not a signed type: {typ}") from e return min_val
def _check_implicit_conversion(self, var_id, sub): target_typ = self.context.vars[var_id].typ assign_typ = sub.typ if isinstance(target_typ, BaseType) and isinstance( assign_typ, BaseType): if not assign_typ.is_literal and assign_typ.typ != target_typ.typ: raise TypeMismatch( f'Invalid type {assign_typ.typ}, expected: {target_typ.typ}', self.stmt)
def subscript(self): sub = Expr.parse_variable_location(self.expr.value, self.context) if isinstance(sub.typ, (MappingType, ListType)): if not isinstance(self.expr.slice, sri_ast.Index): raise StructureException( "Array access must access a single element, not a slice", self.expr, ) index = Expr.parse_value_expr(self.expr.slice.value, self.context) elif isinstance(sub.typ, TupleType): if not isinstance(self.expr.slice.value, sri_ast.Int) or self.expr.slice.value.n < 0 or self.expr.slice.value.n >= len(sub.typ.members): # noqa: E501 raise TypeMismatch("Tuple index invalid", self.expr.slice.value) index = self.expr.slice.value.n else: raise TypeMismatch("Bad subscript attempt", self.expr.value) o = add_variable_offset(sub, index, pos=getpos(self.expr)) o.mutable = sub.mutable return o
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 _to_bytelike(expr, args, kwargs, context, bytetype): if bytetype == 'string': ReturnType = StringType elif bytetype == 'bytes': ReturnType = ByteArrayType else: raise TypeMismatch(f'Invalid {bytetype} supplied') in_arg = args[0] if in_arg.typ.maxlen > args[1].slice.value.n: raise TypeMismatch( f'Cannot convert as input {bytetype} are larger than max length', expr, ) return LLLnode(value=in_arg.value, args=in_arg.args, typ=ReturnType(in_arg.typ.maxlen), pos=getpos(expr), location=in_arg.location)
def pack_logging_topics(event_id, args, expected_topics, context, pos): topics = [event_id] code_pos = pos for pos, expected_topic in enumerate(expected_topics): expected_type = expected_topic.typ arg = args[pos] value = Expr(arg, context).lll_node arg_type = value.typ if isinstance(arg_type, ByteArrayLike) and isinstance(expected_type, ByteArrayLike): if arg_type.maxlen > expected_type.maxlen: raise TypeMismatch( f"Topic input bytes are too big: {arg_type} {expected_type}", code_pos ) if isinstance(arg, sri_ast.Str): bytez, bytez_length = string_to_bytes(arg.s) if len(bytez) > 32: raise InvalidLiteral( "Can only log a maximum of 32 bytes at a time.", code_pos ) topics.append(bytes_to_int(bytez + b'\x00' * (32 - bytez_length))) else: if value.location == "memory": size = ['mload', value] elif value.location == "storage": size = ['sload', ['sha3_32', value]] topics.append(byte_array_to_num(value, arg, 'uint256', size)) else: if arg_type != expected_type: raise TypeMismatch( f"Invalid type for logging topic, got {arg_type} expected {expected_type}", value.pos ) value = unwrap_location(value) value = base_type_conversion(value, arg_type, expected_type, pos=code_pos) topics.append(value) return topics
def _check_valid_assign(self, sub): if (isinstance(self.stmt.annotation, sri_ast.Name) and self.stmt.annotation.id == 'bytes32'): # CMC 04/07/2020 this check could be much clearer, more like: # if isinstance(ByteArrayLike) and maxlen == 32 # or isinstance(BaseType) and typ.typ == 'bytes32' # then GOOD # else RAISE if isinstance(sub.typ, ByteArrayLike): if sub.typ.maxlen != 32: raise TypeMismatch( 'Invalid type, expected: bytes32. String is incorrect length.', self.stmt) return elif isinstance(sub.typ, BaseType): if sub.typ.typ != 'bytes32': raise TypeMismatch('Invalid type, expected: bytes32', self.stmt) return else: raise TypeMismatch("Invalid type, expected: bytes32", self.stmt) elif isinstance(self.stmt.annotation, sri_ast.Subscript): # check list assign: if not isinstance(sub.typ, (ListType, ByteArrayLike)): raise TypeMismatch( f'Invalid type, expected: {self.stmt.annotation.value.id},' f' got: {sub.typ}', self.stmt) elif sub.typ is None: # Check that the object to be assigned is not of NoneType raise TypeMismatch( f"Invalid type, expected {self.stmt.annotation.id}", self.stmt) elif isinstance(sub.typ, StructType): # This needs to get more sophisticated in the presence of # foreign structs. if not sub.typ.name == self.stmt.annotation.id: raise TypeMismatch( f"Invalid type, expected {self.stmt.annotation.id}", self.stmt) # Check that the integer literal, can be assigned to uint256 if necessary. elif (self.stmt.annotation.id, sub.typ.typ) == ('uint256', 'int128') and sub.typ.is_literal: if not SizeLimits.in_bounds('uint256', sub.value): raise InvalidLiteral( 'Invalid uint256 assignment, value not in uint256 range.', self.stmt) elif self.stmt.annotation.id != sub.typ.typ: raise TypeMismatch( f'Invalid type {sub.typ.typ}, expected: {self.stmt.annotation.id}', self.stmt, ) else: return True
def get_external_contract_keywords(stmt_expr, context): from srilang.parser.expr import Expr value, gas = None, None for kw in stmt_expr.keywords: if kw.arg not in ('value', 'gas'): raise TypeMismatch( 'Invalid keyword argument, only "gas" and "value" supported.', stmt_expr, ) elif kw.arg == 'gas': gas = Expr.parse_value_expr(kw.value, context) elif kw.arg == 'value': value = Expr.parse_value_expr(kw.value, context) return value, gas
def parse_assert(self): with self.context.assertion_scope(): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if not self.is_bool_expr(test_expr): raise TypeMismatch('Only boolean expressions allowed', self.stmt.test) if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: return LLLnode.from_list(['assert', test_expr], typ=None, pos=getpos(self.stmt))
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 get_external_contract_call_output(sig, context): if not sig.output_type: return 0, 0, [] output_placeholder = context.new_placeholder(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = [0, output_placeholder] elif isinstance(sig.output_type, ByteArrayLike): returner = [0, output_placeholder + 32] elif isinstance(sig.output_type, TupleLike): returner = [0, output_placeholder] elif isinstance(sig.output_type, ListType): returner = [0, output_placeholder] else: raise TypeMismatch(f"Invalid output type: {sig.output_type}") return output_placeholder, output_size, returner
def unroll_constant(self, const, global_ctx): ann_expr = None expr = Expr.parse_value_expr( const.value, Context(vars=None, global_ctx=global_ctx, origcode=const.full_source_code, memory_allocator=MemoryAllocator()), ) annotation_type = global_ctx.parse_type(const.annotation.args[0], None) fail = False if is_instances([expr.typ, annotation_type], ByteArrayType): if expr.typ.maxlen < annotation_type.maxlen: return const fail = True elif expr.typ != annotation_type: fail = True # special case for literals, which can be uint256 types as well. is_special_case_uint256_literal = (is_instances( [expr.typ, annotation_type], BaseType)) and ([ annotation_type.typ, expr.typ.typ ] == ['uint256', 'int128']) and SizeLimits.in_bounds( 'uint256', expr.value) is_special_case_int256_literal = (is_instances( [expr.typ, annotation_type], BaseType)) and ([ annotation_type.typ, expr.typ.typ ] == ['int128', 'int128']) and SizeLimits.in_bounds( 'int128', expr.value) if is_special_case_uint256_literal or is_special_case_int256_literal: fail = False if fail: raise TypeMismatch( f"Invalid value for constant type, expected {annotation_type} got " f"{expr.typ} instead", const.value, ) ann_expr = copy.deepcopy(expr) ann_expr.typ = annotation_type ann_expr.typ.is_literal = expr.typ.is_literal # Annotation type doesn't have literal set. return ann_expr
def ann_assign(self): with self.context.assignment_scope(): typ = parse_type( self.stmt.annotation, location='memory', custom_structs=self.context.structs, constants=self.context.constants, ) if isinstance(self.stmt.target, sri_ast.Attribute): raise TypeMismatch( f'May not set type for field {self.stmt.target.attr}', self.stmt, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) if self.stmt.value is None: raise StructureException( 'New variables must be initialized explicitly', self.stmt) sub = Expr(self.stmt.value, self.context).lll_node is_literal_bytes32_assign = (isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == 'bytes32' and sub.typ.is_literal) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_literal_bytes32_assign: sub = LLLnode( bytes_to_int(self.stmt.value.s), typ=BaseType('bytes32'), pos=getpos(self.stmt), ) self._check_valid_assign(sub) self._check_same_variable_assign(sub) variable_loc = LLLnode.from_list( pos, typ=typ, location='memory', pos=getpos(self.stmt), ) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) return o
def parse_if(self): if self.stmt.orelse: block_scope_id = id(self.stmt.orelse) with self.context.make_blockscope(block_scope_id): add_on = [parse_body(self.stmt.orelse, self.context)] else: add_on = [] block_scope_id = id(self.stmt) with self.context.make_blockscope(block_scope_id): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if not self.is_bool_expr(test_expr): raise TypeMismatch('Only boolean expressions allowed', self.stmt.test) body = ['if', test_expr, parse_body(self.stmt.body, self.context)] + add_on o = LLLnode.from_list(body, typ=None, pos=getpos(self.stmt)) return o
def assign(self): # Assignment (e.g. x[4] = y) with self.context.assignment_scope(): sub = Expr(self.stmt.value, self.context).lll_node # Error check when assigning to declared variable if isinstance(self.stmt.target, sri_ast.Name): # Do not allow assignment to undefined variables without annotation if self.stmt.target.id not in self.context.vars: raise VariableDeclarationException( "Variable type not defined", self.stmt) # Check against implicit conversion self._check_implicit_conversion(self.stmt.target.id, sub) is_valid_tuple_assign = (isinstance( self.stmt.target, sri_ast.Tuple)) and isinstance( self.stmt.value, sri_ast.Tuple) # Do no allow tuple-to-tuple assignment if is_valid_tuple_assign: raise VariableDeclarationException( "Tuple to tuple assignment not supported", self.stmt, ) # Checks to see if assignment is valid target = self.get_target(self.stmt.target) if isinstance(target.typ, ContractType) and not isinstance( sub.typ, ContractType): raise TypeMismatch( 'Contract assignment expects casted address: ' f'{target.typ}(<address_var>)', self.stmt) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) return o
def to_bool(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == 'bytes': if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to bool", expr, ) else: num = byte_array_to_num(in_arg, expr, 'uint256') return LLLnode.from_list(['iszero', ['iszero', num]], typ=BaseType('bool'), pos=getpos(expr)) else: return LLLnode.from_list(['iszero', ['iszero', in_arg]], typ=BaseType('bool'), pos=getpos(expr))
def call_lookup_specs(stmt_expr, context): from srilang.parser.expr import Expr method_name = stmt_expr.func.attr if len(stmt_expr.keywords): raise TypeMismatch( "Cannot use keyword arguments in calls to functions via 'self'", stmt_expr, ) expr_args = [Expr(arg, context).lll_node for arg in stmt_expr.args] sig = FunctionSignature.lookup_sig( context.sigs, method_name, expr_args, stmt_expr, context, ) return method_name, expr_args, sig
def evaluate(self) -> srilangNode: """ Attempt to evaluate the comparison. Returns ------- NameConstant Node representing the result of the evaluation. """ left, right = self.left, self.right if not isinstance(left, Constant): raise UnfoldableNode( "Node contains invalid field(s) for evaluation") if isinstance(self.op, In): if not isinstance(right, List): raise UnfoldableNode( "Node contains invalid field(s) for evaluation") if next((i for i in right.elts if not isinstance(i, Constant)), None): raise UnfoldableNode( "Node contains invalid field(s) for evaluation") if len(set([type(i) for i in right.elts])) > 1: raise UnfoldableNode("List contains multiple literal types") value = self.op._op(left.value, [i.value for i in right.elts]) return NameConstant.from_node(self, value=value) if not isinstance(left, type(right)): raise UnfoldableNode("Cannot compare different literal types") if not isinstance(self.op, (Eq, NotEq)) and not isinstance( left, (Int, Decimal)): raise TypeMismatch( f"Invalid literal types for {self.op.description} comparison", self) value = self.op._op(left.value, right.value) return NameConstant.from_node(self, value=value)
def to_bytes32(expr, args, kwargs, context): in_arg = args[0] input_type, _len = get_type(in_arg) if input_type == 'bytes': if _len > 32: raise TypeMismatch( f"Unable to convert bytes[{_len}] to bytes32, max length is too " "large.") if in_arg.location == "memory": return LLLnode.from_list(['mload', ['add', in_arg, 32]], typ=BaseType('bytes32')) elif in_arg.location == "storage": return LLLnode.from_list( ['sload', ['add', ['sha3_32', in_arg], 1]], typ=BaseType('bytes32')) else: return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType('bytes32'), pos=getpos(expr))
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)
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 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 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 check_list_type_match(provided): # Check list types match. if provided != typ: raise TypeMismatch( f"Log list type '{provided}' does not match provided, expected '{typ}'" )
def pack_logging_data(expected_data, args, context, pos): # Checks to see if there's any data if not args: return ['seq'], 0, None, 0 holder = ['seq'] maxlen = len(args) * 32 # total size of all packed args (upper limit) # Unroll any function calls, to temp variables. prealloacted = {} for idx, (arg, _expected_arg) in enumerate(zip(args, expected_data)): if isinstance(arg, (sri_ast.Str, sri_ast.Call)): expr = Expr(arg, context) source_lll = expr.lll_node typ = source_lll.typ if isinstance(arg, sri_ast.Str): if len(arg.s) > typ.maxlen: raise TypeMismatch( f"Data input bytes are to big: {len(arg.s)} {typ}", pos ) tmp_variable = context.new_internal_variable( f'_log_pack_var_{arg.lineno}_{arg.col_offset}', source_lll.typ, ) tmp_variable_node = LLLnode.from_list( tmp_variable, typ=source_lll.typ, pos=getpos(arg), location="memory", annotation=f'log_prealloacted {source_lll.typ}', ) # Store len. # holder.append(['mstore', len_placeholder, ['mload', unwrap_location(source_lll)]]) # Copy bytes. holder.append( make_setter(tmp_variable_node, source_lll, pos=getpos(arg), location='memory') ) prealloacted[idx] = tmp_variable_node requires_dynamic_offset = any([isinstance(data.typ, ByteArrayLike) for data in expected_data]) if requires_dynamic_offset: dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) else: dynamic_offset_counter = None # Create placeholder for static args. Note: order of new_*() is important. placeholder_map = {} for i, (_arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if not isinstance(typ, ByteArrayLike): placeholder = context.new_placeholder(typ) else: placeholder = context.new_placeholder(BaseType(32)) placeholder_map[i] = placeholder # Populate static placeholders. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ placeholder = placeholder_map[i] if not isinstance(typ, ByteArrayLike): holder, maxlen = pack_args_by_32( holder, maxlen, prealloacted.get(i, arg), 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 _arg, data in zip(args, expected_data): typ = data.typ if isinstance(typ, ByteArrayLike): maxlen += 32 + ceil32(typ.maxlen) if requires_dynamic_offset: datamem_start = dynamic_placeholder + 32 else: datamem_start = placeholder_map[0] # Copy necessary data into allocated dynamic section. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if isinstance(typ, ByteArrayLike): pack_args_by_32( holder=holder, maxlen=maxlen, arg=prealloacted.get(i, arg), typ=typ, context=context, placeholder=placeholder_map[i], datamem_start=datamem_start, dynamic_offset_counter=dynamic_offset_counter, pos=pos ) return holder, maxlen, dynamic_offset_counter, datamem_start
def _op(self, left, right): if isinstance(left, decimal.Decimal): raise TypeMismatch( "Cannot perform exponentiation on decimal values.", self._parent) return int(left**right)