def canonicalize_type(t, is_indexed=False): if isinstance(t, ByteArrayLike): # Check to see if maxlen is small enough for events byte_type = 'string' if isinstance(t, StringType) else 'bytes' if is_indexed: return f'{byte_type}{t.maxlen}' else: return f'{byte_type}' if isinstance(t, ListType): if not isinstance(t.subtype, (ListType, BaseType)): raise InvalidType(f"List of {t.subtype}s not allowed") return canonicalize_type(t.subtype) + f"[{t.count}]" if isinstance(t, TupleLike): return f"({','.join(canonicalize_type(x) for x in t.tuple_members())})" if not isinstance(t, BaseType): raise InvalidType(f"Cannot canonicalize non-base type: {t}") t = t.typ if t in ('int128', 'uint256', 'bool', 'address', 'bytes32'): return t elif t == 'decimal': return 'fixed168x10' raise InvalidType(f"Invalid or unsupported type: {repr(t)}")
def get_static_size_of_type(typ): if isinstance(typ, BaseType): return 1 elif isinstance(typ, ByteArrayLike): return 1 elif isinstance(typ, ListType): return get_size_of_type(typ.subtype) * typ.count elif isinstance(typ, MappingType): raise InvalidType( "Maps are not supported for function arguments or outputs.") elif isinstance(typ, TupleLike): return sum([get_size_of_type(v) for v in typ.tuple_members()]) else: raise InvalidType( f"Can not get size of type, Unexpected type: {repr(typ)}")
def get_size_of_type(typ): if isinstance(typ, BaseType): return 1 elif isinstance(typ, ByteArrayLike): # 1 word for offset (in static section), 1 word for length, # up to maxlen words for actual data. return ceil32(typ.maxlen) // 32 + 2 elif isinstance(typ, ListType): return get_size_of_type(typ.subtype) * typ.count elif isinstance(typ, MappingType): raise InvalidType( "Maps are not supported for function arguments or outputs.") elif isinstance(typ, TupleLike): return sum([get_size_of_type(v) for v in typ.tuple_members()]) else: raise InvalidType( f"Can not get size of type, Unexpected type: {repr(typ)}")
def has_dynamic_data(typ): if isinstance(typ, BaseType): return False elif isinstance(typ, ByteArrayLike): return True elif isinstance(typ, ListType): return has_dynamic_data(typ.subtype) elif isinstance(typ, TupleLike): return any([has_dynamic_data(v) for v in typ.tuple_members()]) else: raise InvalidType(f"Unexpected type: {repr(typ)}")
def make_struct_type(name, location, members, custom_structs, constants): o = OrderedDict() for key, value in members: if not isinstance(key, sri_ast.Name): raise InvalidType( f"Invalid member variable for struct {key.id}, expected a name.", key, ) check_valid_varname( key.id, custom_structs, constants, "Invalid member variable for struct", ) o[key.id] = parse_type( value, location, custom_structs=custom_structs, constants=constants, ) return StructType(o, name)
def from_declaration(cls, code, global_ctx): name = code.target.id pos = 0 check_valid_varname(name, global_ctx._structs, global_ctx._constants, pos=code, error_prefix="Event name invalid. ", exc=EventDeclarationException) # Determine the arguments, expects something of the form def foo(arg1: num, arg2: num ... args = [] indexed_list = [] topics_count = 1 if code.annotation.args: keys = code.annotation.args[0].keys values = code.annotation.args[0].values for i in range(len(keys)): typ = values[i] if not isinstance(keys[i], sri_ast.Name): raise EventDeclarationException( 'Invalid key type, expected a valid name.', keys[i], ) if not isinstance( typ, (sri_ast.Name, sri_ast.Call, sri_ast.Subscript)): raise EventDeclarationException( 'Invalid event argument type.', typ) if isinstance(typ, sri_ast.Call) and not isinstance( typ.func, sri_ast.Name): raise EventDeclarationException( 'Invalid event argument type', typ) arg = keys[i].id arg_item = keys[i] is_indexed = False # Check to see if argument is a topic if isinstance(typ, sri_ast.Call) and typ.func.id == 'indexed': typ = values[i].args[0] indexed_list.append(True) topics_count += 1 is_indexed = True else: indexed_list.append(False) if isinstance(typ, sri_ast.Subscript) and getattr( typ.value, 'id', None ) == 'bytes' and typ.slice.value.n > 32 and is_indexed: # noqa: E501 raise EventDeclarationException( "Indexed arguments are limited to 32 bytes") if topics_count > 4: raise EventDeclarationException( f"Maximum of 3 topics {topics_count - 1} given", arg, ) if not isinstance(arg, str): raise VariableDeclarationException("Argument name invalid", arg) if not typ: raise InvalidType("Argument must have type", arg) check_valid_varname( arg, global_ctx._structs, global_ctx._constants, pos=arg_item, error_prefix="Event argument name invalid or reserved.", ) if arg in (x.name for x in args): raise VariableDeclarationException( "Duplicate function argument name: " + arg, arg_item, ) # Can struct be logged? parsed_type = global_ctx.parse_type(typ, None) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = name + '(' + ','.join([ canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args) ]) + ')' # noqa F812 event_id = bytes_to_int(keccak256(bytes(sig, 'utf-8'))) return cls(name, args, indexed_list, event_id, sig)
def from_definition(cls, code, sigs=None, custom_structs=None, contract_def=False, constants=None, constant_override=False): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 valid_name, msg = is_varname_valid(name, custom_structs, constants) if not valid_name and (not name.lower() in FUNCTION_WHITELIST): raise FunctionDeclarationException("Function name invalid. " + msg, code) # Validate default values. for default_value in getattr(code.args, 'defaults', []): validate_default_values(default_value) # Determine the arguments, expects something of the form def foo(arg1: # int128, arg2: int128 ... args = [] for arg in code.args.args: # Each arg needs a type specified. typ = arg.annotation if not typ: raise InvalidType("Argument must have type", arg) # Validate arg name. check_valid_varname( arg.arg, custom_structs, constants, arg, "Argument name invalid or reserved. ", FunctionDeclarationException, ) # Check for duplicate arg name. if arg.arg in (x.name for x in args): raise FunctionDeclarationException( "Duplicate function argument name: " + arg.arg, arg, ) parsed_type = parse_type( typ, None, sigs, custom_structs=custom_structs, constants=constants, ) args.append( VariableRecord( arg.arg, mem_pos, parsed_type, False, defined_at=getpos(arg), )) if isinstance(parsed_type, ByteArrayLike): mem_pos += 32 else: mem_pos += get_size_of_type(parsed_type) * 32 const = constant_override payable = False private = False public = False nonreentrant_key = '' # Update function properties from decorators for dec in code.decorator_list: if isinstance(dec, sri_ast.Name) and dec.id == "constant": const = True elif isinstance(dec, sri_ast.Name) and dec.id == "payable": payable = True elif isinstance(dec, sri_ast.Name) and dec.id == "private": private = True elif isinstance(dec, sri_ast.Name) and dec.id == "public": public = True elif isinstance(dec, sri_ast.Call) and dec.func.id == "nonreentrant": if nonreentrant_key: raise StructureException( "Only one @nonreentrant decorator allowed per function", dec) if dec.args and len(dec.args) == 1 and isinstance( dec.args[0], sri_ast.Str) and dec.args[0].s: # noqa: E501 nonreentrant_key = dec.args[0].s else: raise StructureException( "@nonreentrant decorator requires a non-empty string to use as a key.", dec) else: raise StructureException("Bad decorator", dec) if public and private: raise StructureException( f"Cannot use public and private decorators on the same function: {name}" ) if payable and const: raise StructureException( f"Function {name} cannot be both constant and payable.") if payable and private: raise StructureException( f"Function {name} cannot be both private and payable.") if (not public and not private) and not contract_def: raise StructureException( "Function visibility must be declared (@public or @private)", code, ) if const and nonreentrant_key: raise StructureException( "@nonreentrant makes no sense on a @constant function.", code) # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... # def foo() -> int128: ... # If there is no return type, ie. it's of the form def foo(): ... # and NOT def foo() -> type: ..., then it's null if not code.returns: output_type = None elif isinstance(code.returns, (sri_ast.Name, sri_ast.Compare, sri_ast.Subscript, sri_ast.Call, sri_ast.Tuple)): output_type = parse_type( code.returns, None, sigs, custom_structs=custom_structs, constants=constants, ) else: raise InvalidType( f"Output type invalid or unsupported: {parse_type(code.returns, None)}", code.returns, ) # Output type must be canonicalizable if output_type is not None: assert isinstance(output_type, TupleType) or canonicalize_type(output_type) # Get the canonical function signature sig = cls.get_full_sig(name, code.args.args, sigs, custom_structs, constants) # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(keccak256(bytes(sig, 'utf-8'))[:4]) return cls(name, args, output_type, const, payable, private, nonreentrant_key, sig, method_id, code)
def __init__(self, keytype, valuetype): if not isinstance(keytype, (BaseType, ByteArrayLike)): raise InvalidType("Dictionary keys must be a base type") self.keytype = keytype self.valuetype = valuetype
def parse_type(item, location, sigs=None, custom_structs=None, constants=None): # Base and custom types, e.g. num if isinstance(item, sri_ast.Name): if item.id in BASE_TYPES: return BaseType(item.id) elif (custom_structs is not None) and (item.id in custom_structs): return make_struct_type( item.id, location, custom_structs[item.id], custom_structs, constants, ) else: raise InvalidType("Invalid base type: " + item.id, item) # Units, e.g. num (1/sec) or contracts elif isinstance(item, sri_ast.Call) and isinstance(item.func, sri_ast.Name): # Mapping type. if item.func.id == 'map': if location == 'memory': raise InvalidType( "No mappings allowed for in-memory types, only fixed-size arrays", item, ) if len(item.args) != 2: raise InvalidType( "Mapping requires 2 valid positional arguments.", item, ) keytype = parse_type( item.args[0], None, custom_structs=custom_structs, constants=constants, ) if not isinstance(keytype, (BaseType, ByteArrayLike)): raise InvalidType( "Mapping keys must be base or bytes/string types", item) return MappingType( keytype, parse_type( item.args[1], location, custom_structs=custom_structs, constants=constants, ), ) # Contract_types if item.func.id == 'address': if sigs and item.args[0].id in sigs: return ContractType(item.args[0].id) # Struct types if (custom_structs is not None) and (item.func.id in custom_structs): return make_struct_type( item.id, location, custom_structs[item.id], custom_structs, constants, ) raise InvalidType("Units are no longer supported", item) # Subscripts elif isinstance(item, sri_ast.Subscript): # Fixed size lists or bytearrays, e.g. num[100] is_constant_val = constants.ast_is_constant(item.slice.value) if isinstance(item.slice.value, sri_ast.Int) or is_constant_val: n_val = (constants.get_constant(item.slice.value.id, context=None).value if is_constant_val else item.slice.value.n) if not isinstance(n_val, int) or n_val <= 0: raise InvalidType( "Arrays / ByteArrays must have a positive integral number of elements", item.slice.value, ) # ByteArray if getattr(item.value, 'id', None) == 'bytes': return ByteArrayType(n_val) elif getattr(item.value, 'id', None) == 'string': return StringType(n_val) # List else: return ListType( parse_type( item.value, location, custom_structs=custom_structs, constants=constants, ), n_val) # Mappings, e.g. num[address] else: warnings.warn( "Mapping definitions using subscript have deprecated (see VIP564). " "Use map(type1, type2) instead.", DeprecationWarning) raise InvalidType('Unknown list type.', item) # Dicts, used to represent mappings, e.g. {uint: uint}. Key must be a base type elif isinstance(item, sri_ast.Dict): warnings.warn( "Anonymous structs have been removed in" " favor of named structs, see VIP300", DeprecationWarning) raise InvalidType("Invalid type", item) elif isinstance(item, sri_ast.Tuple): members = [ parse_type(x, location, custom_structs=custom_structs, constants=constants) for x in item.elts ] return TupleType(members) else: raise InvalidType("Invalid type", item)