def serialize_full_transaction(transaction, block, transaction_index, is_pending): if is_pending: block_number = None block_hash = None transaction_index = None else: block_number = block['number'] block_hash = block['hash'] serialized_transaction = pipe( transaction, partial(assoc, key='block_number', value=block_number), partial(assoc, key='block_hash', value=block_hash), partial(assoc, key='transaction_index', value=transaction_index), partial(assoc, key='type', value=extract_transaction_type(transaction))) if 'gas_price' in transaction: return serialized_transaction else: # TODO: Sometime in 2022 the inclusion of gas_price may be removed from dynamic fee # transactions and we can get rid of this behavior. # https://github.com/ethereum/execution-specs/pull/251 gas_price = (transaction['max_fee_per_gas'] if is_pending else calculate_effective_gas_price(transaction, block)) return assoc(serialized_transaction, 'gas_price', gas_price)
def map_abi_data( normalizers: Sequence[Callable[[TypeStr, Any], Tuple[TypeStr, Any]]], types: Sequence[TypeStr], data: Sequence[Any], ) -> Any: """ This function will apply normalizers to your data, in the context of the relevant types. Each normalizer is in the format: def normalizer(datatype, data): # Conditionally modify data return (datatype, data) Where datatype is a valid ABI type string, like "uint". In case of an array, like "bool[2]", normalizer will receive `data` as an iterable of typed data, like `[("bool", True), ("bool", False)]`. Internals --- This is accomplished by: 1. Decorating the data tree with types 2. Recursively mapping each of the normalizers to the data 3. Stripping the types back out of the tree """ pipeline = itertools.chain( [abi_data_tree(types)], map(data_tree_map, normalizers), [partial(recursive_map, strip_abi_type)], ) return pipe(data, *pipeline)
def apply_module_to_formatters( formatters: Tuple[Callable[..., TReturn]], module: "Module", method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]], ) -> Iterable[Callable[..., TReturn]]: for f in formatters: yield partial(f, module, method_name)
def serialize_receipt(transaction, block, transaction_index, is_pending): if is_pending: block_number = None block_hash = None transaction_index = None else: block_number = block['number'] block_hash = block['hash'] return pipe( transaction, partial(assoc, key='block_number', value=block_number), partial(assoc, key='block_hash', value=block_hash), partial(assoc, key='transaction_index', value=transaction_index), partial(assoc, key='state_root', value=b'\x00'), )
def mine_blocks(self, num_blocks=1, coinbase=None): for _ in range(num_blocks): block_to_mine = dissoc(self.block, 'hash') block_hash = fake_rlp_hash(block_to_mine) mined_block = assoc(block_to_mine, 'hash', block_hash) assign_block_info = compose( partial(assoc, key='block_number', value=mined_block['number']), partial(assoc, key='block_hash', value=mined_block['hash']), ) mined_block['transactions'] = tuple( assign_block_info(transaction) for transaction in mined_block['transactions']) self.blocks.append(mined_block) self.block = make_block_from_parent(mined_block) yield block_hash
def factory(cls, web3, class_name=None, **kwargs): kwargs['web3'] = web3 normalizers = { 'abi': normalize_abi, 'address': partial(normalize_address, kwargs['web3'].ens), 'bytecode': normalize_bytecode, 'bytecode_runtime': normalize_bytecode, } contract = PropertyCheckingFactory( class_name or cls.__name__, (cls, ), kwargs, normalizers=normalizers, ) contract.functions = ContractFunctions(contract.abi, contract.web3) contract.caller = ContractCaller(contract.abi, contract.web3, contract.address) contract.events = ContractEvents(contract.abi, contract.web3) contract.fallback = Contract.get_fallback_function( contract.abi, contract.web3) return contract
def __init__(self, abi, web3, address, transaction=None, block_identifier='latest'): self.web3 = web3 self.address = address self.abi = abi self._functions = None if self.abi: if transaction is None: transaction = {} self._functions = filter_by_type('function', self.abi) for func in self._functions: fn = ContractFunction.factory(func['name'], web3=self.web3, contract_abi=self.abi, address=self.address, function_identifier=func['name']) block_id = parse_block_identifier(self.web3, block_identifier) caller_method = partial(self.call_function, fn, transaction=transaction, block_identifier=block_id) setattr(self, func['name'], caller_method)
def create_log_filter(self, from_block=None, to_block=None, address=None, topics=None): self.validator.validate_inbound_filter_params( from_block=from_block, to_block=to_block, address=address, topics=topics, ) ( raw_from_block, raw_to_block, raw_address, raw_topics, ) = self.normalizer.normalize_inbound_filter_params( from_block=from_block, to_block=to_block, address=address, topics=topics, ) raw_filter_id = next(self._filter_counter) raw_filter_params = { 'from_block': raw_from_block, 'to_block': raw_to_block, 'addresses': raw_address, 'topics': raw_topics, } filter_fn = partial(check_if_log_matches, **raw_filter_params) new_filter = Filter( filter_params=raw_filter_params, filter_fn=filter_fn, ) self._log_filters[raw_filter_id] = new_filter if is_integer(raw_from_block): if is_integer(raw_to_block): upper_bound = raw_to_block + 1 else: upper_bound = self.get_block_by_number('pending')['number'] for block_number in range(raw_from_block, upper_bound): block = self.get_block_by_number(block_number) self._add_log_entries_to_filter(block, new_filter) filter_id = self.normalizer.normalize_outbound_filter_id(raw_filter_id) return filter_id
def serialize_receipt(receipt, transaction, block, transaction_index, is_pending): if is_pending: block_number = None block_hash = None transaction_index = None else: block_number = block['number'] block_hash = block['hash'] return pipe( receipt, partial(assoc, key='block_number', value=block_number), partial(assoc, key='block_hash', value=block_hash), partial(assoc, key='effective_gas_price', value=( calculate_effective_gas_price(transaction, block) )), partial(assoc, key='from', value=to_bytes(transaction['from'])), partial(assoc, key='state_root', value=b'\x00'), partial(assoc, key='status', value=0), partial(assoc, key='to', value=transaction['to']), partial(assoc, key='transaction_index', value=transaction_index), partial(assoc, key='type', value=extract_transaction_type(transaction)) )
def validate_log_entry_type(value): if value not in {"pending", "mined"}: raise ValidationError( "Log entry type must be one of 'pending' or 'mined'") LOG_ENTRY_VALIDATORS = { "type": validate_log_entry_type, "log_index": validate_positive_integer, "transaction_index": if_not_null(validate_positive_integer), "transaction_hash": validate_32_byte_string, "block_hash": if_not_null(validate_32_byte_string), "block_number": if_not_null(validate_positive_integer), "address": validate_canonical_address, "data": validate_bytes, "topics": partial(validate_array, validator=validate_32_byte_string), } validate_log_entry = partial(validate_dict, key_validators=LOG_ENTRY_VALIDATORS) def validate_signature_v(value): validate_positive_integer(value) if value not in [0, 1, 27, 28] and value not in range(35, UINT256_MAX + 1): raise ValidationError( "The `v` portion of the signature must be 0, 1, 27, 28 or >= 35") def validate_y_parity(value): validate_positive_integer(value)
if TYPE_CHECKING: from web3 import Web3 # noqa: F401 from web3.module import Module # noqa: F401 from web3.eth import Eth # noqa: F401 def bytes_to_ascii(value: bytes) -> str: return codecs.decode(value, 'ascii') to_ascii_if_bytes = apply_formatter_if(is_bytes, bytes_to_ascii) to_integer_if_hex = apply_formatter_if(is_string, hex_to_integer) to_hex_if_integer = apply_formatter_if(is_integer, integer_to_hex) is_false = partial(operator.is_, False) is_not_false = complement(is_false) is_not_null = complement(is_null) @curry def to_hexbytes(num_bytes: int, val: Union[str, int, bytes], variable_length: bool = False) -> HexBytes: if isinstance(val, (str, int, bytes)): result = HexBytes(val) else: raise TypeError("Cannot convert %r to HexBytes" % val) extra_bytes = len(result) - num_bytes
from eth_utils.toolz import partial from eth.vm.forks.berlin import constants as berlin_constants from eth.vm.forks.berlin.logic import ( GAS_SCHEDULE_EIP2929, sstore_eip2929_generic, ) SSTORE_CLEARS_SCHEDULE_EIP_3529 = ( GAS_SCHEDULE_EIP2929.sstore_reset_gas + berlin_constants.ACCESS_LIST_STORAGE_KEY_COST_EIP_2930) GAS_SCHEDULE_EIP3529 = GAS_SCHEDULE_EIP2929._replace( sstore_clears_schedule=SSTORE_CLEARS_SCHEDULE_EIP_3529) sstore_eip3529 = partial(sstore_eip2929_generic, GAS_SCHEDULE_EIP3529)
from eth_utils.toolz import ( compose, identity, partial, ) from .common import ( normalize_if, normalize_dict, normalize_array, ) from ..utils.encoding import int_to_32byte_big_endian normalize_account = to_checksum_address normalize_account_list = partial(normalize_array, normalizer=normalize_account) to_empty_or_checksum_address = apply_one_of_formatters(( (lambda addr: addr == b'', lambda addr: ''), (is_canonical_address, to_checksum_address), )) def _normalize_outbound_access_list(access_list): return tuple([{ 'address': to_checksum_address(entry[0]), 'storage_keys': tuple([encode_hex(int_to_32byte_big_endian(k)) for k in entry[1]]) } for entry in access_list])
)) TRANSACTION_NORMALIZERS = { 'from': to_canonical_address, 'to': to_empty_or_canonical_address, 'gas': identity, 'gas_price': identity, 'nonce': identity, 'value': identity, 'data': decode_hex, 'r': identity, 's': identity, 'v': identity, } normalize_transaction = partial(normalize_dict, normalizers=TRANSACTION_NORMALIZERS) LOG_ENTRY_NORMALIZERS = { 'type': identity, 'log_index': identity, 'transaction_index': identity, 'transaction_hash': decode_hex, 'block_hash': partial(normalize_if, conditional_fn=is_string, normalizer=decode_hex), 'block_number': identity, 'address':
TRANSACTION_RESULT_KEY_MAPPING = { 'access_list': 'accessList', 'block_hash': 'blockHash', 'block_number': 'blockNumber', 'gas_price': 'gasPrice', 'max_fee_per_gas': 'maxFeePerGas', 'max_priority_fee_per_gas': 'maxPriorityFeePerGas', 'transaction_hash': 'transactionHash', 'transaction_index': 'transactionIndex', } transaction_result_remapper = apply_key_map(TRANSACTION_RESULT_KEY_MAPPING) TRANSACTION_RESULT_FORMATTERS = { 'to': apply_formatter_if(partial(operator.eq, ''), static_return(None)), 'access_list': apply_formatter_to_array( apply_key_map({'storage_keys': 'storageKeys'}), ), } transaction_result_formatter = apply_formatters_to_dict(TRANSACTION_RESULT_FORMATTERS) LOG_RESULT_KEY_MAPPING = { 'log_index': 'logIndex', 'transaction_index': 'transactionIndex', 'transaction_hash': 'transactionHash', 'block_hash': 'blockHash', 'block_number': 'blockNumber', } log_result_remapper = apply_key_map(LOG_RESULT_KEY_MAPPING)
import pytest from eth_utils.toolz import ( partial, ) from web3._utils.blocks import ( select_method_for_block_identifier, ) selector_fn = partial( select_method_for_block_identifier, if_hash='test_hash', if_number='test_number', if_predefined='test_predefined', ) @pytest.mark.parametrize( 'input,expected', ( ('latest', 'test_predefined'), ('pending', 'test_predefined'), ('earliest', 'test_predefined'), (-1, ValueError), (0, 'test_number'), (1, 'test_number'), (4000000, 'test_number'), ('0x0', 'test_number'), ('0x00', 'test_number'), ('0x1', 'test_number'), ('0x01', 'test_number'), (hex(4000000), 'test_number'),
def validate_log_entry_type(value): if value not in {"pending", "mined"}: raise ValidationError( "Log entry type must be one of 'pending' or 'mined'") LOG_ENTRY_VALIDATORS = { "type": validate_log_entry_type, "log_index": validate_positive_integer, "transaction_index": if_not_null(validate_positive_integer), "transaction_hash": validate_32_byte_string, "block_hash": if_not_null(validate_32_byte_string), "block_number": if_not_null(validate_positive_integer), "address": validate_canonical_address, "data": validate_bytes, "topics": partial(validate_array, validator=validate_32_byte_string), } validate_log_entry = partial(validate_dict, key_validators=LOG_ENTRY_VALIDATORS) def validate_signature_v(value): validate_positive_integer(value) if value not in [0, 1, 27, 28] and value not in range(35, UINT256_MAX + 1): raise ValidationError( "The `v` portion of the signature must be 0, 1, 27, 28 or >= 35") TRANSACTION_VALIDATORS = {
} filter_params_remapper = apply_key_map(FILTER_PARAMS_MAPPINGS) FILTER_PARAMS_FORMATTERS = { 'fromBlock': to_integer_if_hex, 'toBlock': to_integer_if_hex, } filter_params_formatter = apply_formatters_to_dict(FILTER_PARAMS_FORMATTERS) filter_params_transformer = compose(filter_params_remapper, filter_params_formatter) TRANSACTION_FORMATTERS = { 'to': apply_formatter_if(partial(operator.eq, ''), static_return(None)), # type: ignore } transaction_formatter = apply_formatters_to_dict(TRANSACTION_FORMATTERS) RECEIPT_FORMATTERS = { 'logs': apply_formatter_to_array(log_key_remapper), } receipt_formatter = apply_formatters_to_dict(RECEIPT_FORMATTERS) transaction_params_transformer = compose(transaction_params_remapper, transaction_params_formatter) ethereum_tester_middleware = construct_formatting_middleware(
def validate_block_number(value): error_message = ( "Block number must be a positive integer or one of the strings " "'latest', 'earliest', or 'pending'. Got: {}".format(value) ) if is_string(value): validate_text(value) if value not in BLOCK_NUMBER_META_VALUES: raise ValidationError(error_message) elif not is_integer(value) or is_boolean(value): raise ValidationError(error_message) elif value < 0: raise ValidationError(error_message) validate_block_hash = partial(validate_32_byte_hex_value, name="Block hash") validate_transaction_hash = partial(validate_32_byte_hex_value, name="Transaction hash") validate_filter_id = partial(validate_positive_integer) def validate_account(value): if not is_text(value): raise ValidationError("Address must be 20 bytes encoded as hexidecimal") elif not is_hex_address(value): raise ValidationError("Address must be 20 bytes encoded as hexidecimal") elif is_checksum_formatted_address(value) and not is_checksum_address(value): raise ValidationError("Address does not validate EIP55 checksum") def is_valid_topic_array(value): if not is_list_like(value):
def get_logs(self, from_block=None, to_block=None, address=None, topics=None): self.validator.validate_inbound_filter_params( from_block=from_block, to_block=to_block, address=address, topics=topics, ) ( raw_from_block, raw_to_block, raw_address, raw_topics, ) = self.normalizer.normalize_inbound_filter_params( from_block=from_block, to_block=to_block, address=address, topics=topics, ) # Setup the filter object raw_filter_params = { 'from_block': raw_from_block, 'to_block': raw_to_block, 'addresses': raw_address, 'topics': raw_topics, } filter_fn = partial( check_if_log_matches, **raw_filter_params, ) log_filter = Filter( filter_params=raw_filter_params, filter_fn=filter_fn, ) # Set from/to block defaults if raw_from_block is None: raw_from_block = 'latest' if raw_to_block is None: raw_to_block = 'latest' # Determine lower bound for block range. if isinstance(raw_from_block, int): lower_bound = raw_from_block else: lower_bound = self.get_block_by_number(raw_from_block)['number'] # Determine upper bound for block range. if isinstance(raw_to_block, int): upper_bound = raw_to_block else: upper_bound = self.get_block_by_number(raw_to_block)['number'] # Enumerate the blocks in the block range to find all log entries which match. for block_number in range(lower_bound, upper_bound + 1): block = self.get_block_by_number(block_number) for transaction_hash in block['transactions']: receipt = self.get_transaction_receipt(transaction_hash) for log_entry in receipt['logs']: raw_log_entry = self.normalizer.normalize_inbound_log_entry(log_entry) log_filter.add(raw_log_entry) # Return the matching log entries for item in log_filter.get_all(): yield self.normalizer.normalize_outbound_log_entry(item)