def select_filter_method( value: Union[str, FilterParams, HexStr], if_new_block_filter: RPCEndpoint, if_new_pending_transaction_filter: RPCEndpoint, if_new_filter: RPCEndpoint, ) -> RPCEndpoint: if is_string(value): if value == "latest": return if_new_block_filter elif value == "pending": return if_new_pending_transaction_filter elif is_hex(value): raise _UseExistingFilter(value) else: raise ValidationError( "Filter argument needs to be either 'latest'," " 'pending', or a hex-encoded filter_id. Filter argument" f" is: {value}") elif isinstance(value, dict): return if_new_filter else: raise ValidationError( "Filter argument needs to be either the string " "'pending' or 'latest', a filter_id, " f"or a filter params dictionary. Filter argument is: {value}")
def handle_offchain_lookup( offchain_lookup_payload: Dict[str, Any], transaction: TxParams, ) -> bytes: formatted_sender = to_hex_if_bytes( offchain_lookup_payload['sender']).lower() formatted_data = to_hex_if_bytes( offchain_lookup_payload['callData']).lower() if formatted_sender != to_hex_if_bytes(transaction['to']).lower(): raise ValidationError( 'Cannot handle OffchainLookup raised inside nested call. Returned `sender` value does ' 'not equal `to` address in transaction.') for url in offchain_lookup_payload['urls']: formatted_url = URI( str(url).replace('{sender}', str(formatted_sender)).replace( '{data}', str(formatted_data))) try: if '{data}' in url and '{sender}' in url: response = get_response_from_get_request(formatted_url) elif '{sender}' in url: response = get_response_from_post_request(formatted_url, data={ "data": formatted_data, "sender": formatted_sender, }) else: raise ValidationError('url not formatted properly.') except Exception: continue # try next url if timeout or issues making the request if 400 <= response.status_code <= 499: # if request returns 400 error, raise exception response.raise_for_status() if not 200 <= response.status_code <= 299: # if not 400 error, try next url continue result = response.json() if 'data' not in result.keys(): raise ValidationError( "Improperly formatted response for offchain lookup HTTP request - missing 'data' " "field.") encoded_data_with_function_selector = b''.join([ # 4-byte callback function selector to_bytes_if_hex(offchain_lookup_payload['callbackFunction']), # encode the `data` from the result and the `extraData` as bytes encode_abi(['bytes', 'bytes'], [ to_bytes_if_hex(result['data']), to_bytes_if_hex(offchain_lookup_payload['extraData']), ]) ]) return encoded_data_with_function_selector raise MultipleFailedRequests("Offchain lookup failed for supplied urls.")
def _validate_chain_id(web3_chain_id: int, chain_id: int) -> int: if to_integer_if_hex(chain_id) == web3_chain_id: return chain_id else: raise ValidationError( f"The transaction declared chain ID {chain_id!r}, " f"but the connected node is on {web3_chain_id!r}")
def attach_modules( parent_module: Union["Web3", "Module"], module_definitions: Dict[str, Sequence[Any]], w3: Optional[Union["Web3", "Module"]] = None ) -> None: for module_name, module_info in module_definitions.items(): module_class = module_info[0] if hasattr(parent_module, module_name): raise AttributeError( f"Cannot set {parent_module} module named '{module_name}'. The web3 object " "already has an attribute with that name" ) if w3 is None: setattr(parent_module, module_name, module_class(parent_module)) w3 = parent_module else: setattr(parent_module, module_name, module_class(w3)) if len(module_info) == 2: submodule_definitions = module_info[1] module = getattr(parent_module, module_name) attach_modules(module, submodule_definitions, w3) elif len(module_info) != 1: raise ValidationError("Module definitions can only have 1 or 2 elements.")
def find_matching_fn_abi(abi, abi_codec, fn_identifier=None, args=None, kwargs=None): args = args or tuple() kwargs = kwargs or dict() num_arguments = len(args) + len(kwargs) if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) if not is_text(fn_identifier): raise TypeError("Unsupported function identifier") name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) encoding_filter = functools.partial(filter_by_encodability, abi_codec, args, kwargs) function_candidates = pipe(abi, name_filter, arg_count_filter, encoding_filter) if len(function_candidates) == 1: return function_candidates[0] else: matching_identifiers = name_filter(abi) matching_function_signatures = [ abi_to_signature(func) for func in matching_identifiers ] arg_count_matches = len(arg_count_filter(matching_identifiers)) encoding_matches = len(encoding_filter(matching_identifiers)) if arg_count_matches == 0: diagnosis = "\nFunction invocation failed due to improper number of arguments." elif encoding_matches == 0: diagnosis = "\nFunction invocation failed due to no matching argument types." elif encoding_matches > 1: diagnosis = ( "\nAmbiguous argument encoding. " "Provided arguments can be encoded to multiple functions matching this call." ) message = ( "\nCould not identify the intended function with name `{name}`, " "positional argument(s) of type `{arg_types}` and " "keyword argument(s) of type `{kwarg_types}`." "\nFound {num_candidates} function(s) with the name `{name}`: {candidates}" "{diagnosis}").format( name=fn_identifier, arg_types=tuple(map(type, args)), kwarg_types=valmap(type, kwargs), num_candidates=len(matching_identifiers), candidates=matching_function_signatures, diagnosis=diagnosis, ) raise ValidationError(message)
def __init__( self, endpoint_uri: Optional[Union[URI, str]] = None, websocket_kwargs: Any = None, websocket_timeout: int = DEFAULT_WEBSOCKET_TIMEOUT, ) -> None: self.endpoint_uri = URI(endpoint_uri) self.websocket_timeout = websocket_timeout if self.endpoint_uri is None: self.endpoint_uri = get_default_endpoint() if WebsocketProvider._loop is None: WebsocketProvider._loop = _get_threaded_loop() if websocket_kwargs is None: websocket_kwargs = {} else: found_restricted_keys = set(websocket_kwargs.keys()).intersection( RESTRICTED_WEBSOCKET_KWARGS) if found_restricted_keys: raise ValidationError( '{0} are not allowed in websocket_kwargs, ' 'found: {1}'.format(RESTRICTED_WEBSOCKET_KWARGS, found_restricted_keys)) self.conn = PersistentWebSocket(self.endpoint_uri, WebsocketProvider._loop, websocket_kwargs) super().__init__()
def buy_at_fixed_rate( self, amount: float, wallet: Wallet, max_OCEAN_amount: float, exchange_id: str = "", data_token: str = "", exchange_owner: str = "", ) -> bool: exchange, exchange_id = self.get_exchange_id_fallback_dt_and_owner( exchange_id, exchange_owner, data_token) amount_base = to_base_18(amount) max_OCEAN_amount_base = to_base_18(max_OCEAN_amount) # Figure out the amount of ocean tokens to approve before triggering the exchange function to do the swap ocean_amount_base = exchange.get_base_token_quote( exchange_id, amount_base) if ocean_amount_base > max_OCEAN_amount_base: raise ValidationError( f"Buying {amount} datatokens requires {from_base_18(ocean_amount_base)} OCEAN " f"tokens which exceeds the max_OCEAN_amount {max_OCEAN_amount}." ) ocean_token = DataToken(self.ocean_address) ocean_token.get_tx_receipt( ocean_token.approve(self._exchange_address, ocean_amount_base, wallet)) tx_id = exchange.buy_data_token(exchange_id, data_token_amount=amount_base, from_wallet=wallet) return bool(exchange.get_tx_receipt(tx_id).status)
def find_matching_fn_abi( abi: ABI, abi_codec: ABICodec, fn_identifier: Optional[Union[str, Type[FallbackFn], Type[ReceiveFn]]] = None, args: Optional[Sequence[Any]] = None, kwargs: Optional[Any] = None, ) -> ABIFunction: args = args or tuple() kwargs = kwargs or dict() num_arguments = len(args) + len(kwargs) if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) if fn_identifier is ReceiveFn: return get_receive_func_abi(abi) if not is_text(fn_identifier): raise TypeError("Unsupported function identifier") name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) encoding_filter = functools.partial(filter_by_encodability, abi_codec, args, kwargs) function_candidates = pipe(abi, name_filter, arg_count_filter, encoding_filter) if len(function_candidates) == 1: return function_candidates[0] else: matching_identifiers = name_filter(abi) matching_function_signatures = [ abi_to_signature(func) for func in matching_identifiers ] arg_count_matches = len(arg_count_filter(matching_identifiers)) encoding_matches = len(encoding_filter(matching_identifiers)) if arg_count_matches == 0: diagnosis = "\nFunction invocation failed due to improper number of arguments." elif encoding_matches == 0: diagnosis = "\nFunction invocation failed due to no matching argument types." elif encoding_matches > 1: diagnosis = ( "\nAmbiguous argument encoding. " "Provided arguments can be encoded to multiple functions matching this call." ) message = ( f"\nCould not identify the intended function with name `{fn_identifier}`, positional " f"argument(s) of type `{tuple(map(type, args))}` and keyword argument(s) of type " f"`{valmap(type, kwargs)}`.\nFound {len(matching_identifiers)} function(s) with " f"the name `{fn_identifier}`: {matching_function_signatures}{diagnosis}" ) raise ValidationError(message)
def _validate_chain_id(web3_chain_id: int, chain_id: int) -> int: if to_integer_if_hex(chain_id) == web3_chain_id: return chain_id else: raise ValidationError("The transaction declared chain ID %r, " "but the connected node is on %r" % ( chain_id, web3_chain_id, ))
def validate_chain_id(web3, chain_id): if chain_id == web3.net.chainId: return chain_id else: raise ValidationError("The transaction declared chain ID %r, " "but the connected node is on %r" % ( chain_id, "UNKNOWN", ))
def validate_chain_id(web3, chain_id): if chain_id == web3.version.network: return None else: raise ValidationError("The transaction declared chain ID %r, " "but the connected node is on %r" % ( chain_id, web3.version.network, ))
def validate_chain_id(web3, chain_id): if int(chain_id) == web3.eth.chainId: return chain_id else: raise ValidationError("The transaction declared chain ID %r, " "but the connected node is on %r" % ( chain_id, web3.eth.chainId, ))
def _get_avg_block_time(w3, sample_size): latest = w3.eth.getBlock('latest') constrained_sample_size = min(sample_size, latest['number']) if constrained_sample_size == 0: raise ValidationError('Constrained sample size is 0') oldest = w3.eth.getBlock(latest['number'] - constrained_sample_size) return (latest['timestamp'] - oldest['timestamp']) / constrained_sample_size
def validate_payable(transaction, abi): """Raise ValidationError if non-zero ether is sent to a non payable function. """ if 'value' in transaction: if transaction['value'] != 0: if "payable" in abi and not abi["payable"]: raise ValidationError( "Sending non-zero ether to a contract function " "with payable=False. Please ensure that " "transaction's value is 0.")
def attach_modules(parent_module, module_definitions): for module_name, module_info in module_definitions.items(): module_class = module_info[0] module_class.attach(parent_module, module_name) if len(module_info) == 2: submodule_definitions = module_info[1] module = getattr(parent_module, module_name) attach_modules(module, submodule_definitions) elif len(module_info) != 1: raise ValidationError( "Module definitions can only have 1 or 2 elements.")
def check_extradata_length(val): if not isinstance(val, (str, int, bytes)): return val result = HexBytes(val) if len(result) > MAX_EXTRADATA_LENGTH: raise ValidationError( "The field extraData is %d bytes, but should be %d. " "It is quite likely that you are connected to a POA chain. " "Refer " "http://web3py.readthedocs.io/en/stable/middleware.html#geth-style-proof-of-authority " "for more details. The full extraData is: %r" % (len(result), MAX_EXTRADATA_LENGTH, result)) return val
def _get_weighted_avg_block_time(w3: Web3, sample_size: int) -> float: latest_block_number = w3.eth.getBlock('latest')['number'] constrained_sample_size = min(sample_size, latest_block_number) if constrained_sample_size == 0: raise ValidationError('Constrained sample size is 0') oldest_block = w3.eth.getBlock(BlockNumber(latest_block_number - constrained_sample_size)) oldest_block_number = oldest_block['number'] prev_timestamp = oldest_block['timestamp'] weighted_sum = 0.0 sum_of_weights = 0.0 for i in range(oldest_block_number + 1, latest_block_number + 1): curr_timestamp = w3.eth.getBlock(BlockNumber(i))['timestamp'] time = curr_timestamp - prev_timestamp weight = (i - oldest_block_number) / constrained_sample_size weighted_sum += (time * weight) sum_of_weights += weight prev_timestamp = curr_timestamp return weighted_sum / sum_of_weights
def __init__(self, endpoint_uri=None, websocket_kwargs=None): self.endpoint_uri = endpoint_uri if self.endpoint_uri is None: self.endpoint_uri = get_default_endpoint() if WebsocketProvider._loop is None: WebsocketProvider._loop = _get_threaded_loop() if websocket_kwargs is None: websocket_kwargs = {} else: found_restricted_keys = set(websocket_kwargs.keys()).intersection( RESTRICTED_WEBSOCKET_KWARGS ) if found_restricted_keys: raise ValidationError( '{0} are not allowed in websocket_kwargs, ' 'found: {1}'.format(RESTRICTED_WEBSOCKET_KWARGS, found_restricted_keys) ) self.conn = PersistentWebSocket( self.endpoint_uri, WebsocketProvider._loop, websocket_kwargs ) super().__init__()
def buy_at_fixed_rate( self, amount: int, wallet: Wallet, max_OCEAN_amount: int, exchange_id: Optional[Union[bytes, str]] = "", data_token: Optional[str] = "", exchange_owner: Optional[str] = "", ) -> bool: exchange, exchange_id = self.get_exchange_id_fallback_dt_and_owner( exchange_id, exchange_owner, data_token) # Figure out the amount of ocean tokens to approve before triggering the exchange function to do the swap ocean_amount = exchange.get_base_token_quote(exchange_id, amount) ocean_token = DataToken(self._web3, self.ocean_address) ocean_ticker = ocean_token.symbol() if ocean_amount > max_OCEAN_amount: raise ValidationError( f"Buying {pretty_ether_and_wei(amount, 'DataTokens')} requires {pretty_ether_and_wei(ocean_amount, ocean_ticker)} " f"tokens which exceeds the max_OCEAN_amount {pretty_ether_and_wei(max_OCEAN_amount, ocean_ticker)}." ) if ocean_token.balanceOf(wallet.address) < ocean_amount: raise InsufficientBalance( f"Insufficient funds for buying {pretty_ether_and_wei(amount, 'DataTokens')}!" ) if ocean_token.allowance(wallet.address, self._exchange_address) < ocean_amount: tx_id = ocean_token.approve(self._exchange_address, ocean_amount, wallet) tx_receipt = ocean_token.get_tx_receipt(self._web3, tx_id) if not tx_receipt or tx_receipt.status != 1: raise VerifyTxFailed( f"Approve OCEAN tokens failed, exchange address was {self._exchange_address} and tx id was {tx_id}!" ) tx_id = exchange.buy_data_token(exchange_id, data_token_amount=amount, from_wallet=wallet) return bool(exchange.get_tx_receipt(self._web3, tx_id).status)
def getLogs( self, event, web3, argument_filters: Optional[Dict[str, Any]] = None, fromBlock: Optional[BlockIdentifier] = None, toBlock: Optional[BlockIdentifier] = None, blockHash: Optional[HexBytes] = None, ): """Get events for this contract instance using eth_getLogs API. This is a stateless method, as opposed to createFilter. It can be safely called against nodes which do not provide eth_newFilter API, like Infura nodes. If there are many events, like ``Transfer`` events for a popular token, the Ethereum node might be overloaded and timeout on the underlying JSON-RPC call. Example - how to get all ERC-20 token transactions for the latest 10 blocks: .. code-block:: python from = max(mycontract.web3.eth.blockNumber - 10, 1) to = mycontract.web3.eth.blockNumber events = mycontract.events.Transfer.getLogs(fromBlock=from, toBlock=to) for e in events: print(e["args"]["from"], e["args"]["to"], e["args"]["value"]) The returned processed log values will look like: .. code-block:: python ( AttributeDict({ 'args': AttributeDict({}), 'event': 'LogNoArguments', 'logIndex': 0, 'transactionIndex': 0, 'transactionHash': HexBytes('...'), 'address': '0xF2E246BB76DF876Cef8b38ae84130F4F55De395b', 'blockHash': HexBytes('...'), 'blockNumber': 3 }), AttributeDict(...), ... ) See also: :func:`web3.middleware.filter.local_filter_middleware`. :param argument_filters: :param fromBlock: block number or "latest", defaults to "latest" :param toBlock: block number or "latest". Defaults to "latest" :param blockHash: block hash. blockHash cannot be set at the same time as fromBlock or toBlock :yield: Tuple of :class:`AttributeDict` instances """ if not self.address: raise TypeError( "This method can be only called on " "an instated contract with an address" ) abi = event._get_event_abi() if argument_filters is None: argument_filters = dict() _filters = dict(**argument_filters) blkhash_set = blockHash is not None blknum_set = fromBlock is not None or toBlock is not None if blkhash_set and blknum_set: raise ValidationError( "blockHash cannot be set at the same" " time as fromBlock or toBlock" ) # Construct JSON-RPC raw filter presentation based on human readable Python descriptions # Namely, convert event names to their keccak signatures _, event_filter_params = construct_event_filter_params( abi, contract_address=self.address, argument_filters=_filters, fromBlock=fromBlock, toBlock=toBlock, ) if blockHash is not None: event_filter_params["blockHash"] = blockHash # Call JSON-RPC API logs = web3.eth.getLogs(event_filter_params) # Convert raw binary data to Python proxy objects as described by ABI return tuple(get_event_data(abi, entry) for entry in logs)