def get_hash( self, _from: bytes, _to: bytes, _ids: List[int], _from_values: List[int], _to_values: List[int], _value_eth_wei: int, _nonce: int, ) -> bytes: """ Generate a hash mirroring the way we are creating this in the contract. :param _from: the from address hashed :param _to: the to address hashed :param _ids: the token ids :param _from_values: the from values :param _to_values: the to values :param _value_eth_wei: the value of eth (in wei) :param _nonce: the trade nonce :return: the hash in bytes string representation """ aggregate_hash = keccak256( b"".join( [ _ids[0].to_bytes(32, "big"), _from_values[0].to_bytes(32, "big"), _to_values[0].to_bytes(32, "big"), ] ) ) for i in range(len(_ids)): if not i == 0: aggregate_hash = keccak256( b"".join( [ aggregate_hash, _ids[i].to_bytes(32, "big"), _from_values[i].to_bytes(32, "big"), _to_values[i].to_bytes(32, "big"), ] ) ) m_list = [] m_list.append(_from) m_list.append(_to) m_list.append(aggregate_hash) m_list.append(_value_eth_wei.to_bytes(32, "big")) m_list.append(_nonce.to_bytes(32, "big")) return keccak256(b"".join(m_list))
def _assert_reason(self, test_expr, msg): if isinstance(msg, vy_ast.Name) and msg.id == "UNREACHABLE": return LLLnode.from_list(["assert_unreachable", test_expr], typ=None, pos=getpos(msg)) reason_str_type = ByteArrayType(len(msg.value.strip())) sig_placeholder = self.context.new_internal_variable(BaseType(32)) arg_placeholder = self.context.new_internal_variable(BaseType(32)) placeholder_bytes = Expr(msg, self.context).lll_node method_id = fourbytes_to_int(keccak256(b"Error(string)")[:4]) revert_seq = [ "seq", ["mstore", sig_placeholder, method_id], ["mstore", arg_placeholder, 32], placeholder_bytes, [ "revert", sig_placeholder + 28, int(4 + get_size_of_type(reason_str_type) * 32) ], ] if test_expr: lll_node = ["if", ["iszero", test_expr], revert_seq] else: lll_node = revert_seq return LLLnode.from_list(lll_node, typ=None, pos=getpos(self.stmt))
def _assert_reason(self, test_expr, msg): if isinstance(msg, vy_ast.Name) and msg.id == "UNREACHABLE": return LLLnode.from_list(["assert_unreachable", test_expr], typ=None, pos=getpos(msg)) 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 keccak256_helper(expr, ir_arg, context): sub = ir_arg # TODO get rid of useless variable _check_byteslike(sub.typ, expr) # Can hash literals # TODO this is dead code. if isinstance(sub, bytes): return IRnode.from_list(bytes_to_int(keccak256(sub)), typ=BaseType("bytes32")) # Can hash bytes32 objects if is_base_type(sub.typ, "bytes32"): return IRnode.from_list( [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, sub], ["sha3", MemoryPositions.FREE_VAR_SPACE, 32], ], typ=BaseType("bytes32"), add_gas_estimate=_gas_bound(1), ) sub = ensure_in_memory(sub, context) return IRnode.from_list( [ "with", "_buf", sub, ["sha3", ["add", "_buf", 32], ["mload", "_buf"]], ], typ=BaseType("bytes32"), annotation="keccak256", add_gas_estimate=_gas_bound(ceil(sub.typ.maxlen / 32)), )
def method_id(self) -> int: """ Four byte selector for this function. """ function_sig = f"{self.name}({','.join(i.canonical_type for i in self.arguments.values())})" selector = keccak256(function_sig.encode())[:4].hex() return int(selector, 16)
def _assert_reason(self, test_expr, msg): if isinstance(msg, ast.Name) and msg.id == 'UNREACHABLE': return self._assert_unreachable(test_expr, msg) if not isinstance(msg, 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 + 32 + get_size_of_type(reason_str_type) * 32), ], ] return LLLnode.from_list(assert_reason, typ=None, pos=getpos(self.stmt))
def keccak256_helper(expr, to_hash, context): _check_byteslike(to_hash.typ, expr) # Can hash literals # TODO this is dead code. if isinstance(to_hash, bytes): return IRnode.from_list(bytes_to_int(keccak256(to_hash)), typ=BaseType("bytes32")) # Can hash bytes32 objects if is_base_type(to_hash.typ, "bytes32"): return IRnode.from_list( [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, to_hash], ["sha3", MemoryPositions.FREE_VAR_SPACE, 32], ], typ=BaseType("bytes32"), add_gas_estimate=_gas_bound(1), ) to_hash = ensure_in_memory(to_hash, context) with to_hash.cache_when_complex("buf") as (b1, to_hash): data = bytes_data_ptr(to_hash) len_ = get_bytearray_length(to_hash) return b1.resolve( IRnode.from_list( ["sha3", data, len_], typ="bytes32", annotation="keccak256", add_gas_estimate=_gas_bound(ceil(to_hash.typ.maxlen / 32)), ))
def get_single_hash( self, _from: bytes, _to: bytes, _id: int, _from_value: int, _to_value: int, _value_eth_wei: int, _nonce: int, ) -> bytes: """ Generate a hash mirroring the way we are creating this in the contract. :param _from: the from address hashed :param _to: the to address hashed :param _ids: the token ids :param _from_value: the from value :param _to_value: the to value :param _value_eth_wei: the value eth (in wei) :param _nonce: the trade nonce :return: the hash in bytes string representation """ return keccak256(b"".join([ _from, _to, _id.to_bytes(32, "big"), _from_value.to_bytes(32, "big"), _to_value.to_bytes(32, "big"), _value_eth_wei.to_bytes(32, "big"), _nonce.to_bytes(32, "big"), ]))
def __init__(self, name: str, arguments: OrderedDict, indexed: List) -> None: for key in arguments: validate_identifier(key) self.name = name self.arguments = arguments self.indexed = indexed self.event_id = int(keccak256(self.signature.encode()).hex(), 16)
def __init__(self, name: str, arguments: OrderedDict, indexed: List) -> None: for key in arguments: validate_identifier(key) self.name = name self.arguments = arguments self.indexed = indexed signature = f"{name}({','.join(v.canonical_type for v in arguments.values())})" self.event_id = int(keccak256(signature.encode()).hex(), 16)
def from_declaration(cls, class_node, global_ctx): name = class_node.name pos = 0 check_valid_varname( name, global_ctx._structs, global_ctx._constants, pos=class_node, error_prefix="Event name invalid. ", exc=EventDeclarationException, ) args = [] indexed_list = [] if len(class_node.body) != 1 or not isinstance(class_node.body[0], vy_ast.Pass): for node in class_node.body: arg_item = node.target arg = node.target.id typ = node.annotation if isinstance(typ, vy_ast.Call) and typ.get("func.id") == "indexed": indexed_list.append(True) typ = typ.args[0] else: indexed_list.append(False) 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 TypeCheckFailure( f"Duplicate function argument name: {arg}") # 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 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_internal_variable(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 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("Unsupported location: %s" % 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 pack_logging_topics(event_id, arg_nodes, arg_types, context): topics = [event_id] for node, typ in zip(arg_nodes, arg_types): value = Expr(node, context).lll_node if isinstance(typ, ArrayValueAbstractType): if isinstance(node, (vy_ast.Str, vy_ast.Bytes)): # for literals, generate the topic at compile time value = node.value if isinstance(value, str): value = value.encode() topics.append(bytes_to_int(keccak256(value))) elif value.location == "memory": topics.append(["sha3", ["add", value, 32], ["mload", value]]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") copier = make_byte_array_copier( placeholder_node, LLLnode.from_list("_sub", typ=value.typ, location=value.location), ) lll_node = [ "with", "_sub", value, ["seq", copier, ["sha3", ["add", placeholder, 32], ["mload", placeholder]]], ] topics.append(lll_node) elif isinstance(typ, ArrayDefinition): size = typ.size_in_bytes if value.location == "memory": topics.append(["sha3", value, size]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") setter = make_setter(placeholder_node, value, "memory", value.pos) lll_node = ["seq", setter, ["sha3", placeholder, size]] topics.append(lll_node) else: value = unwrap_location(value) topics.append(value) return topics
def test_get_extcodehash(get_contract, evm_version, no_optimize): code = """ a: address @external def __init__(): self.a = self @external def foo(x: address) -> bytes32: return x.codehash @external def foo2(x: address) -> bytes32: b: address = x return b.codehash @external def foo3() -> bytes32: return self.codehash @external def foo4() -> bytes32: return self.a.codehash """ if evm_version in ("byzantium", "atlantis"): with pytest.raises(EvmVersionException): compile_code(code, evm_version=evm_version) return compiled = compile_code(code, ["bytecode_runtime"], evm_version=evm_version, no_optimize=no_optimize) bytecode = bytes.fromhex(compiled["bytecode_runtime"][2:]) hash_ = keccak256(bytecode) c = get_contract(code, evm_version=evm_version) assert c.foo(c.address) == hash_ assert not int( c.foo("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").hex(), 16) assert c.foo2(c.address) == hash_ assert not int( c.foo2("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").hex(), 16) assert c.foo3() == hash_ assert c.foo4() == hash_
def method_id(expr, args, kwargs, context): if b' ' in args[0]: raise TypeMismatchException('Invalid function signature no spaces allowed.') method_id = fourbytes_to_int(keccak256(args[0])[:4]) if args[1] == 'bytes32': return LLLnode(method_id, typ=BaseType('bytes32'), pos=getpos(expr)) elif args[1] == 'bytes[4]': placeholder = LLLnode.from_list(context.new_placeholder(ByteArrayType(4))) return LLLnode.from_list( ['seq', ['mstore', ['add', placeholder, 4], method_id], ['mstore', placeholder, 4], placeholder], typ=ByteArrayType(4), location='memory', pos=getpos(expr)) else: raise StructureException('Can only produce bytes32 or bytes[4] as outputs')
def test_handle_response(self): """Test the _handle_response method of the http handler to a valid fetch beacon response.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) test_response = { "result": { "block_id": { "hash": "00000000" }, "block": { "header": { "height": "1", "entropy": { "group_signature": "SIGNATURE" }, } }, } } incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version="", status_code=200, status_text="", headers="", body=json.dumps(test_response).encode("utf-8"), ) # handle message self.http_handler.handle(incoming_message) # check that data was correctly entered into shared state beacon_data = { "entropy": keccak256("SIGNATURE".encode("utf-8")), "block_hash": bytes.fromhex("00000000"), "block_height": 1, } assert self.http_handler.context.shared_state[ "oracle_data"] == beacon_data # check that outbox is empty self.assert_quantity_in_outbox(0)
def test_create_minimal_proxy_to_create(get_contract): code = """ main: address @external def test() -> address: self.main = create_minimal_proxy_to(self) return self.main """ c = get_contract(code) address_bits = int(c.address, 16) nonce = 1 rlp_encoded = rlp.encode([address_bits, nonce]) expected_create_address = keccak256(rlp_encoded)[12:].rjust(20, b"\x00") assert c.test() == checksum_encode("0x" + expected_create_address.hex())
def _handle_response(self, message: HttpMessage) -> None: """ Handle an http response. :param msg: the http message to be handled :return: None """ msg_body = json.loads(message.body) # get entropy and block data entropy = ( msg_body.get("result", {}) .get("block", {}) .get("header", {}) .get("entropy", {}) .get("group_signature", None) ) block_hash = msg_body.get("result", {}).get("block_id", {}).get("hash", {}) block_height_str = ( msg_body.get("result", {}) .get("block", {}) .get("header", {}) .get("height", None) ) if block_height_str: block_height = int(block_height_str) # type: Optional[int] else: block_height = None if entropy is None: self.context.logger.info("entropy not present") elif block_height is None: # pragma: nocover self.context.logger.info("block height not present") else: beacon_data = { "entropy": keccak256(entropy.encode("utf-8")), "block_hash": bytes.fromhex(block_hash), "block_height": block_height, } self.context.logger.info( "Beacon info: " + str({"block_height": block_height, "entropy": entropy}) ) self.context.shared_state["oracle_data"] = beacon_data
def test_contracts_keccak(): hash_ = keccak256(FOO_CODE.encode()).hex() input_json = { 'sources': { 'foo.vy': { 'content': FOO_CODE, 'keccak256': hash_ } } } get_input_dict_contracts(input_json) input_json['sources']['foo.vy']['keccak256'] = "0x" + hash_ get_input_dict_contracts(input_json) input_json['sources']['foo.vy']['keccak256'] = "0x1234567890" with pytest.raises(JSONError): get_input_dict_contracts(input_json)
def test_contracts_keccak(): hash_ = keccak256(FOO_CODE.encode()).hex() input_json = { "sources": { "foo.vy": { "content": FOO_CODE, "keccak256": hash_ } } } get_input_dict_contracts(input_json) input_json["sources"]["foo.vy"]["keccak256"] = "0x" + hash_ get_input_dict_contracts(input_json) input_json["sources"]["foo.vy"]["keccak256"] = "0x1234567890" with pytest.raises(JSONError): get_input_dict_contracts(input_json)
def get_input_dict_contracts(input_dict: Dict) -> ContractCodes: contract_sources: ContractCodes = {} for path, value in input_dict['sources'].items(): if 'urls' in value: raise JSONError(f"{path} - 'urls' is not a supported field, use 'content' instead") if 'content' not in value: raise JSONError(f"{path} missing required field - 'content'") if 'keccak256' in value: hash_ = value['keccak256'].lower() if hash_.startswith('0x'): hash_ = hash_[2:] if hash_ != keccak256(value['content'].encode('utf-8')).hex(): raise JSONError( f"Calculated keccak of '{path}' does not match keccak given in input JSON" ) key = _standardize_path(path) if key in contract_sources: raise JSONError(f"Contract namespace collision: {key}") contract_sources[key] = value['content'] return contract_sources
def test_raw_call_bytes32_data(w3, tester, get_contract_with_gas_estimation): code = """ b: uint256 @external def foo(): a: uint256 = 1234 self.b = 4321 raw_log([], convert(a, bytes32)) raw_log([], convert(self.b, bytes32)) raw_log([], convert(b"testmessage", bytes32)) raw_log([], keccak256(b"")) """ c = get_contract_with_gas_estimation(code) tx_hash = c.foo(transact={}) receipt = tester.get_transaction_receipt(tx_hash.hex()) logs = receipt["logs"] assert logs[0]["data"] == w3.toHex((1234).to_bytes(32, "big")) assert logs[1]["data"] == w3.toHex((4321).to_bytes(32, "big")) assert logs[2]["data"] == w3.toHex(b"testmessage").ljust(32 * 2 + 2, "0") assert logs[3]["data"] == w3.toHex(keccak256(b""))
def from_definition( cls, code, sigs=None, custom_structs=None, interface_def=False, constant_override=False, is_from_json=False, ): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 # 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 parsed_type = parse_type( typ, None, sigs, custom_structs=custom_structs, ) 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 mutability = "nonpayable" # Assume nonpayable by default nonreentrant_key = "" is_internal = False # Update function properties from decorators # NOTE: Can't import enums here because of circular import for dec in code.decorator_list: if isinstance(dec, vy_ast.Name) and dec.id in ("payable", "view", "pure"): mutability = dec.id elif isinstance(dec, vy_ast.Name) and dec.id == "internal": is_internal = True elif isinstance(dec, vy_ast.Name) and dec.id == "external": is_internal = False elif isinstance(dec, vy_ast.Call) and dec.func.id == "nonreentrant": nonreentrant_key = dec.args[0].s if constant_override: # In case this override is abused, match previous behavior if mutability == "payable": raise StructureException( f"Function {name} cannot be both constant and payable.") mutability = "view" # 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 output_type = None if code.returns: output_type = parse_type( code.returns, None, sigs, custom_structs=custom_structs, ) # Output type must be canonicalizable 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) # 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, mutability, is_internal, nonreentrant_key, sig, method_id, code, is_from_json, )
# ------------------------------------------------------------------------------ """This module contains the class to connect to an Oracle contract.""" import logging from typing import Any, Dict from vyper.utils import keccak256 from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi from aea.crypto.ethereum import EthereumApi PUBLIC_ID = PublicId.from_str("fetchai/oracle:0.4.0") CONTRACT_ROLE = keccak256(b"ORACLE_ROLE") _default_logger = logging.getLogger( "aea.packages.fetchai.contracts.oracle.contract") class FetchOracleContract(Contract): """The Fetch oracle contract.""" @classmethod def get_grant_role_transaction( cls, ledger_api: LedgerApi, contract_address: Address, oracle_address: Address, gas: int = 0, ) -> JSONLike:
def _generate_method_id(name: str, canonical_abi_types: List[str]) -> Dict[str, int]: function_sig = f"{name}({','.join(canonical_abi_types)})" selector = keccak256(function_sig.encode())[:4].hex() return {function_sig: int(selector, 16)}
def from_definition( cls, code, sigs=None, custom_structs=None, interface_def=False, constants=None, constant_override=False, is_from_json=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 mutability = "nonpayable" # Assume nonpayable by default internal = False external = False nonreentrant_key = "" # Update function properties from decorators # NOTE: Can't import enums here because of circular import for dec in code.decorator_list: if isinstance(dec, vy_ast.Name) and dec.id in ("payable", "view", "pure"): mutability = dec.id elif isinstance(dec, vy_ast.Name) and dec.id == "internal": internal = True elif isinstance(dec, vy_ast.Name) and dec.id == "external": external = True elif isinstance(dec, vy_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], vy_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 constant_override: # In case this override is abused, match previous behavior if mutability == "payable": raise StructureException(f"Function {name} cannot be both constant and payable.") mutability = "view" if external and internal: raise StructureException( f"Cannot use external and internal decorators on the same function: {name}" ) if mutability == "payable" and internal: raise StructureException(f"Function {name} cannot be both internal and payable.") if (not external and not internal) and not interface_def: raise StructureException( "Function visibility must be declared (@external or @internal)", code, ) if mutability in ("view", "pure") and nonreentrant_key: raise StructureException( f"@nonreentrant makes no sense on a @{mutability} 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, (vy_ast.Name, vy_ast.Compare, vy_ast.Subscript, vy_ast.Call, vy_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, mutability, internal, nonreentrant_key, sig, method_id, code, is_from_json, )
def from_definition(cls, code, sigs=None, custom_units=None, custom_structs=None, contract_def=False, constants=None, constant=False): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 valid_name, msg = is_varname_valid(name, custom_units, 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', []): allowed_types = (ast.Num, ast.Str, ast.Bytes, ast.List, ast.NameConstant) if not isinstance(default_value, allowed_types): raise FunctionDeclarationException( "Default parameter values have to be literals.") # 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 InvalidTypeException("Argument must have type", arg) # Validate arg name. check_valid_varname( arg.arg, custom_units, 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_units=custom_units, 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 # Apply decorators const, payable, private, public, nonreentrant_key = False, False, False, False, '' for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True elif isinstance(dec, ast.Name) and dec.id == "payable": payable = True elif isinstance(dec, ast.Name) and dec.id == "private": private = True elif isinstance(dec, ast.Name) and dec.id == "public": public = True elif isinstance(dec, ast.Call) and dec.func.id == "nonreentrant": if dec.args and len(dec.args) == 1 and isinstance( dec.args[0], 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( "Cannot use public and private decorators on the same function: {}" .format(name)) if payable and const: raise StructureException( "Function {} cannot be both constant and payable.".format( name)) if payable and private: raise StructureException( "Function {} cannot be both private and payable.".format(name)) if (not public and not private) and not contract_def: raise StructureException( "Function visibility must be declared (@public or @private)", code, ) if constant and nonreentrant_key: raise StructureException( "@nonreentrant makes no sense on a @constant function.", code) if constant: const = True # 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, (ast.Name, ast.Compare, ast.Subscript, ast.Call, ast.Tuple)): output_type = parse_type( code.returns, None, sigs, custom_units=custom_units, custom_structs=custom_structs, constants=constants, ) else: raise InvalidTypeException( "Output type invalid or unsupported: %r" % 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_units, 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, custom_units, code)
def from_declaration(cls, code, global_ctx): name = code.target.id pos = 0 check_valid_varname(name, global_ctx._custom_units, 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], ast.Name): raise EventDeclarationException( 'Invalid key type, expected a valid name.', keys[i], ) if not isinstance(typ, (ast.Name, ast.Call, ast.Subscript)): raise EventDeclarationException( 'Invalid event argument type.', typ) if isinstance(typ, ast.Call) and not isinstance(typ.func, 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, 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, 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 InvalidTypeException("Argument must have type", arg) check_valid_varname( arg, global_ctx._custom_units, 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 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, (vy_ast.Str, vy_ast.Bytes)): # for literals, generate the topic at compile time value = arg.value if isinstance(value, str): value = value.encode() topics.append(bytes_to_int(keccak256(value))) elif value.location == "memory": topics.append(["sha3", ["add", value, 32], ["mload", value]]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") copier = make_byte_array_copier( placeholder_node, LLLnode.from_list("_sub", typ=value.typ, location=value.location), ) lll_node = [ "with", "_sub", value, ["seq", copier, ["sha3", ["add", placeholder, 32], ["mload", placeholder]]], ] topics.append(lll_node) elif isinstance(arg_type, ListType) and isinstance(expected_type, ListType): size = get_size_of_type(value.typ) * 32 if value.location == "memory": topics.append(["sha3", value, size]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") setter = make_setter(placeholder_node, value, "memory", value.pos) lll_node = ["seq", setter, ["sha3", placeholder, size]] topics.append(lll_node) 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