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 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 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 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 to_address(expr, args, kwargs, context): in_arg = args[0] return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType('address'), pos=getpos(expr))
def _assert_reason(self, test_expr, msg): if isinstance(msg, sri_ast.Name) and msg.id == 'UNREACHABLE': return self._assert_unreachable(test_expr, msg) if not isinstance(msg, sri_ast.Str): raise StructureException( 'Reason parameter of assert needs to be a literal string ' '(or UNREACHABLE constant).', msg) if len(msg.s.strip()) == 0: raise StructureException('Empty reason string not allowed.', self.stmt) reason_str = msg.s.strip() sig_placeholder = self.context.new_placeholder(BaseType(32)) arg_placeholder = self.context.new_placeholder(BaseType(32)) reason_str_type = ByteArrayType(len(reason_str)) placeholder_bytes = Expr(msg, self.context).lll_node method_id = fourbytes_to_int(keccak256(b"Error(string)")[:4]) assert_reason = [ 'seq', ['mstore', sig_placeholder, method_id], ['mstore', arg_placeholder, 32], placeholder_bytes, [ 'assert_reason', test_expr, int(sig_placeholder + 28), int(4 + get_size_of_type(reason_str_type) * 32), ], ] return LLLnode.from_list(assert_reason, typ=None, pos=getpos(self.stmt))
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 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 parse_body(code, context): if not isinstance(code, list): return parse_stmt(code, context) o = ['seq'] for stmt in code: lll = parse_stmt(stmt, context) o.append(lll) o.append('pass') # force zerovalent, even last statement return LLLnode.from_list(o, pos=getpos(code[0]) if code else None)
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 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 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 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 _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 to_uint256(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == 'num_literal': if isinstance(in_arg, int): if not SizeLimits.in_bounds('uint256', in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list(in_arg, typ=BaseType('uint256', ), pos=getpos(expr)) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds('uint256', math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType('uint256'), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif isinstance(in_arg, LLLnode) and input_type == 'int128': return LLLnode.from_list(['clampge', in_arg, 0], typ=BaseType('uint256'), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == 'decimal': return LLLnode.from_list( ['div', ['clampge', in_arg, 0], DECIMAL_DIVISOR], typ=BaseType('uint256'), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == 'bool': return LLLnode.from_list(in_arg, typ=BaseType('uint256'), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ('bytes32', 'address'): return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType('uint256'), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == 'bytes': if in_arg.typ.maxlen > 32: raise InvalidLiteral( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint256", expr, ) return byte_array_to_num(in_arg, expr, 'uint256') else: raise InvalidLiteral(f"Invalid input for uint256: {in_arg}", expr)
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 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 _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 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))