From 0a898fe64a44d99a38ae5aa72d64d7893de2b4e0 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 3 Mar 2022 20:50:09 -0500 Subject: [PATCH 01/39] init commit of async contract --- tests/core/contracts/conftest.py | 52 +++ web3/contract.py | 501 ++++++++++++++++++------ web3/providers/eth_tester/main.py | 50 ++- web3/providers/eth_tester/middleware.py | 244 +++++++----- 4 files changed, 617 insertions(+), 230 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 2941572be8..29d1fbd4e0 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -5,7 +5,14 @@ from eth_utils import ( event_signature_to_log_topic, ) +from eth_utils.toolz import ( + identity, +) +from ens import ( + AsyncENS, +) +from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, CONTRACT_EMITTER_CODE, @@ -46,6 +53,15 @@ REVERT_CONTRACT_BYTECODE, REVERT_CONTRACT_RUNTIME_CODE, ) +from web3.contract import ( + AsyncContract, +) +from web3.eth import ( + AsyncEth, +) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) CONTRACT_NESTED_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; @@ -1036,3 +1052,39 @@ def estimateGas(request): @pytest.fixture def buildTransaction(request): return functools.partial(invoke_contract, api_call_desig='buildTransaction') + + +async def async_deploy(web3, Contract, apply_func=identity, args=None): + args = args or [] + deploy_txn = await Contract.constructor(*args).transact() + deploy_receipt = await web3.eth.wait_for_transaction_receipt(deploy_txn) + assert deploy_receipt is not None + address = apply_func(deploy_receipt['contractAddress']) + contract = Contract(address=address) + assert contract.address == address + assert len(await web3.eth.get_code(contract.address)) > 0 + return contract + + +@pytest.fixture() +async def async_w3(): + provider = AsyncEthereumTesterProvider() + w3 = Web3(provider, modules={'eth': [AsyncEth]}, + middlewares=provider.middlewares) + w3.ens = AsyncENS.fromWeb3(w3) + w3.eth.default_account = await w3.eth.coinbase + return w3 + + +@pytest.fixture() +def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): + contract = AsyncContract.factory(async_w3, + abi=MATH_ABI, + bytecode=MATH_CODE, + bytecode_runtime=MATH_RUNTIME) + return contract + + +@pytest.fixture() +async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): + return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) diff --git a/web3/contract.py b/web3/contract.py index 38e98b4152..89765e2448 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -151,27 +151,33 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] -class ContractFunctions: +class BaseContractFunctions: """Class containing contract function objects """ - def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None) -> None: + def __init__(self, + abi: ABI, + w3: 'Web3', + contract_function_class: Union[Type['ContractFunction'], + Type['AsyncContractFunction']], + address: Optional[ChecksumAddress] = None) -> None: + self.abi = abi self.w3 = w3 self.address = address if self.abi: self._functions = filter_by_type('function', self.abi) - for func in self._functions: - setattr( - self, + for func in self._functions: + setattr( + self, + func['name'], + contract_function_class.factory( func['name'], - ContractFunction.factory( - func['name'], - w3=self.w3, - contract_abi=self.abi, - address=self.address, - function_identifier=func['name'])) + w3=self.w3, + contract_abi=self.abi, + address=self.address, + function_identifier=func['name'])) def __iter__(self) -> Generator[str, None, None]: if not hasattr(self, '_functions') or not self._functions: @@ -208,6 +214,24 @@ def __hasattr__(self, event_name: str) -> bool: return False +class ContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, w3, ContractFunction, address) + + +class AsyncContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, w3, AsyncContractFunction, address) + + class ContractEvents: """Class containing contract event objects @@ -276,7 +300,7 @@ def __hasattr__(self, event_name: str) -> bool: return False -class Contract: +class BaseContract: """Base class for Contract proxy classes. First you need to create your Contract classes using @@ -326,45 +350,18 @@ class Contract: def __init__(self, address: Optional[ChecksumAddress] = None) -> None: """Create a new smart contract proxy object. - :param address: Contract address as 0x hex string - """ - if self.w3 is None: - raise AttributeError( - 'The `Contract` class has not been initialized. Please use the ' - '`web3.contract` interface to create your contract class.' - ) - - if address: - self.address = normalize_address(self.w3.ens, address) - - if not self.address: - raise TypeError("The address argument is required to instantiate a contract.") + :param address: Contract address as 0x hex string""" - self.functions = ContractFunctions(self.abi, self.w3, self.address) self.caller = ContractCaller(self.abi, self.w3, self.address) self.events = ContractEvents(self.abi, self.w3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) @classmethod - def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': - - kwargs['w3'] = w3 + def factory(cls, + contract: Union['Contract', 'AsyncContract'] + ) -> Union['Contract', 'AsyncContract']: - normalizers = { - 'abi': normalize_abi, - 'address': partial(normalize_address, kwargs['w3'].ens), - 'bytecode': normalize_bytecode, - 'bytecode_runtime': normalize_bytecode, - } - - contract = cast(Contract, PropertyCheckingFactory( - class_name or cls.__name__, - (cls,), - kwargs, - normalizers=normalizers, - )) - contract.functions = ContractFunctions(contract.abi, contract.w3) contract.caller = ContractCaller(contract.abi, contract.w3, contract.address) contract.events = ContractEvents(contract.abi, contract.w3) contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3) @@ -583,6 +580,87 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, return deploy_data +class Contract(BaseContract): + + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + if self.w3 is None: + raise AttributeError( + 'The `Contract` class has not been initialized. Please use the ' + '`web3.contract` interface to create your contract class.' + ) + + if address: + self.address = normalize_address(self.w3.ens, address) + + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + + self.functions = ContractFunctions(self.abi, self.w3, self.address) + super().__init__(address) + + @classmethod + def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + kwargs['w3'] = w3 + + normalizers = { + 'abi': normalize_abi, + 'address': partial(normalize_address, kwargs['w3'].ens), + 'bytecode': normalize_bytecode, + 'bytecode_runtime': normalize_bytecode, + } + + contract = cast(Contract, PropertyCheckingFactory( + class_name or cls.__name__, + (cls,), + kwargs, + normalizers=normalizers, + )) + contract.functions = ContractFunctions(contract.abi, contract.w3) + contract = BaseContract.factory(contract) + return contract + + +class AsyncContract(BaseContract): + + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + if self.w3 is None: + raise AttributeError( + 'The `Contract` class has not been initialized. Please use the ' + '`web3.contract` interface to create your contract class.' + ) + + if address: + self.address = normalize_address(self.w3.ens, address) + + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + self.functions = AsyncContractFunctions(self.abi, self.w3, self.address) + super().__init__(address) + + @classmethod + def factory(cls, w3: 'Web3', + class_name: Optional[str] = None, + **kwargs: Any) -> 'AsyncContract': + kwargs['w3'] = w3 + + normalizers = { + 'abi': normalize_abi, + 'address': partial(normalize_address, kwargs['w3'].ens), + 'bytecode': normalize_bytecode, + 'bytecode_runtime': normalize_bytecode, + } + + contract = cast(AsyncContract, PropertyCheckingFactory( + class_name or cls.__name__, + (cls,), + kwargs, + normalizers=normalizers, + )) + contract.functions = AsyncContractFunctions(contract.abi, contract.w3) + contract = BaseContract.factory(contract) + return contract + + def mk_collision_prop(fn_name: str) -> Callable[[], None]: def collision_fn() -> NoReturn: msg = "Namespace collision for function name {0} with ConciseContract API.".format(fn_name) @@ -591,7 +669,7 @@ def collision_fn() -> NoReturn: return collision_fn -class ContractConstructor: +class BaseContractConstructor: """ Class for contract constructor API. """ @@ -644,8 +722,7 @@ def estimateGas( estimate_gas_transaction, block_identifier=block_identifier ) - @combomethod - def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + def _get_transaction(self, transaction: Optional[TxParams] = None) -> HexBytes: if transaction is None: transact_transaction: TxParams = {} else: @@ -659,8 +736,7 @@ def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: transact_transaction['data'] = self.data_in_transaction - # TODO: handle asynchronous contract creation - return self.w3.eth.send_transaction(transact_transaction) + return transact_transaction @combomethod def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: @@ -694,6 +770,20 @@ def check_forbidden_keys_in_transaction( ) +class ContractConstructor(BaseContractConstructor): + + @combomethod + def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return self.w3.eth.send_transaction(self._get_transaction(transaction)) + + +class AsyncContractConstructor(BaseContractConstructor): + + @combomethod + async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return await self.w3.eth.send_transaction(self._get_transaction(transaction)) + + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} @@ -844,7 +934,7 @@ def __getattr__(self, attr: Any) -> Callable[[], None]: return self._raise_exception -class ContractFunction: +class BaseContractFunction: """Base class for contract functions A function accessed via the api contract.functions.myMethod(*args, **kwargs) @@ -897,35 +987,8 @@ def _set_function_info(self) -> None: self.arguments = merge_args_and_kwargs(self.abi, self.args, self.kwargs) - def call( - self, transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest', - state_override: Optional[CallOverrideParams] = None, - ) -> Any: - """ - Execute a contract function call using the `eth_call` interface. + def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams: - This method prepares a ``Caller`` object that exposes the contract - functions and public variables as callable Python functions. - - Reading a public ``owner`` address variable example: - - .. code-block:: python - - ContractFactory = w3.eth.contract( - abi=wallet_contract_definition["abi"] - ) - - # Not a real contract address - contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") - - # Read "owner" public variable - addr = contract.functions.owner().call() - - :param transaction: Dictionary of transaction info for web3 interface - :return: ``Caller`` object that has contract public functions - and variables exposed as Python methods - """ if transaction is None: call_transaction: TxParams = {} else: @@ -952,21 +1015,7 @@ def call( "Please ensure that this contract instance has an address." ) - block_id = parse_block_identifier(self.w3, block_identifier) - - return call_contract_function( - self.w3, - self.address, - self._return_data_normalizers, - self.function_identifier, - call_transaction, - block_id, - self.contract_abi, - self.abi, - state_override, - *self.args, - **self.kwargs - ) + return call_transaction def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: if transaction is None: @@ -1106,6 +1155,104 @@ def __repr__(self) -> str: return '' % self.fn_name +class ContractFunction(BaseContractFunction): + + def call(self, transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest', + state_override: Optional[CallOverrideParams] = None, + ) -> Any: + """ + Execute a contract function call using the `eth_call` interface. + + This method prepares a ``Caller`` object that exposes the contract + functions and public variables as callable Python functions. + + Reading a public ``owner`` address variable example: + + .. code-block:: python + + ContractFactory = w3.eth.contract( + abi=wallet_contract_definition["abi"] + ) + + # Not a real contract address + contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") + + # Read "owner" public variable + addr = contract.functions.owner().call() + + :param transaction: Dictionary of transaction info for web3 interface + :return: ``Caller`` object that has contract public functions + and variables exposed as Python methods + """ + call_transaction = self._get_call_txparams(transaction) + + block_id = parse_block_identifier(self.w3, block_identifier) + + return call_contract_function(self.w3, + self.address, + self._return_data_normalizers, + self.function_identifier, + call_transaction, + block_id, + self.contract_abi, + self.abi, + state_override, + *self.args, + **self.kwargs + ) + + +class AsyncContractFunction(BaseContractFunction): + + async def call( + self, transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest', + state_override: Optional[CallOverrideParams] = None, + ) -> Any: + """ + Execute a contract function call using the `eth_call` interface. + + This method prepares a ``Caller`` object that exposes the contract + functions and public variables as callable Python functions. + + Reading a public ``owner`` address variable example: + + .. code-block:: python + + ContractFactory = w3.eth.contract( + abi=wallet_contract_definition["abi"] + ) + + # Not a real contract address + contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") + + # Read "owner" public variable + addr = contract.functions.owner().call() + + :param transaction: Dictionary of transaction info for web3 interface + :return: ``Caller`` object that has contract public functions + and variables exposed as Python methods + """ + call_transaction = self._get_call_txparams(transaction) + + block_id = await async_parse_block_identifier(self.w3, block_identifier) + + return await async_call_contract_function( + self.w3, + self.address, + self._return_data_normalizers, + self.function_identifier, + call_transaction, + block_id, + self.contract_abi, + self.abi, + state_override, + *self.args, + **self.kwargs + ) + + class ContractEvent: """Base class for contract events @@ -1348,7 +1495,7 @@ def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: return PropertyCheckingFactory(class_name, (cls,), kwargs) -class ContractCaller: +class BaseContractCaller: """ An alternative Contract API. @@ -1375,7 +1522,9 @@ def __init__(self, w3: 'Web3', address: ChecksumAddress, transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest') -> None: + block_identifier: BlockIdentifier = 'latest', + contract_function_class: Optional[BaseContractFunction] = ContractFunction + ) -> None: self.w3 = w3 self.address = address self.abi = abi @@ -1387,7 +1536,7 @@ def __init__(self, self._functions = filter_by_type('function', self.abi) for func in self._functions: - fn: ContractFunction = ContractFunction.factory( + fn: BaseContractFunction = contract_function_class.factory( func['name'], w3=self.w3, contract_abi=self.abi, @@ -1453,6 +1602,29 @@ def call_function( return fn(*args, **kwargs).call(transaction, block_identifier) +class ContractCaller(BaseContractCaller): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, w3, address, + transaction, block_identifier, ContractFunction) + + +class AsyncContractCaller(BaseContractCaller): + + def __init__(self, + abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, w3, address, + transaction, block_identifier, AsyncContractFunction) + + def check_for_forbidden_api_filter_arguments( event_abi: ABIEvent, _filters: Dict[str, Any] ) -> None: @@ -1471,39 +1643,19 @@ def check_for_forbidden_api_filter_arguments( "method.") -def call_contract_function( - w3: 'Web3', - address: ChecksumAddress, - normalizers: Tuple[Callable[..., Any], ...], - function_identifier: FunctionIdentifier, - transaction: TxParams, - block_id: Optional[BlockIdentifier] = None, - contract_abi: Optional[ABI] = None, - fn_abi: Optional[ABIFunction] = None, - state_override: Optional[CallOverrideParams] = None, - *args: Any, - **kwargs: Any) -> Any: +def _call_contract_function(w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + return_data: Union[bytes, bytearray], + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> Any: """ Helper function for interacting with a contract function using the `eth_call` API. """ - call_transaction = prepare_transaction( - address, - w3, - fn_identifier=function_identifier, - contract_abi=contract_abi, - fn_abi=fn_abi, - transaction=transaction, - fn_args=args, - fn_kwargs=kwargs, - ) - - return_data = w3.eth.call( - call_transaction, - block_identifier=block_id, - state_override=state_override, - ) - if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) @@ -1541,6 +1693,94 @@ def call_contract_function( return normalized_data +def call_contract_function( + w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + transaction: TxParams, + block_id: Optional[BlockIdentifier] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + state_override: Optional[CallOverrideParams] = None, + *args: Any, + **kwargs: Any) -> Any: + """ + Helper function for interacting with a contract function using the + `eth_call` API. + """ + call_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = w3.eth.call( + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(w3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + +async def async_call_contract_function( + w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + transaction: TxParams, + block_id: Optional[BlockIdentifier] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + state_override: Optional[CallOverrideParams] = None, + *args: Any, + **kwargs: Any) -> Any: + """ + Helper function for interacting with a contract function using the + `eth_call` API. + """ + call_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = await w3.eth.call( + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(w3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier: if isinstance(block_identifier, int): return parse_block_identifier_int(w3, block_identifier) @@ -1552,6 +1792,19 @@ def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> Blo raise BlockNumberOutofRange +async def async_parse_block_identifier(w3: 'Web3', + block_identifier: BlockIdentifier + ) -> BlockIdentifier: + if isinstance(block_identifier, int): + return parse_block_identifier_int(w3, block_identifier) + elif block_identifier in ['latest', 'earliest', 'pending']: + return block_identifier + elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier): + return await w3.eth.get_block(block_identifier)['number'] + else: + raise BlockNumberOutofRange + + def parse_block_identifier_int(w3: 'Web3', block_identifier_int: int) -> BlockNumber: if block_identifier_int >= 0: block_num = block_identifier_int diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index e45df5082b..987b6e0000 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -17,18 +17,26 @@ from web3._utils.compat import ( Literal, ) +from web3.middleware.buffered_gas_estimate import ( + async_buffered_gas_estimate_middleware, +) from web3.providers import ( BaseProvider, ) from web3.providers.async_base import ( AsyncBaseProvider, ) +from web3.providers.eth_tester.defaults import ( + API_ENDPOINTS, +) from web3.types import ( RPCEndpoint, RPCResponse, ) from .middleware import ( + async_default_transaction_fields_middleware, + async_ethereum_tester_middleware, default_transaction_fields_middleware, ethereum_tester_middleware, ) @@ -43,13 +51,45 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): + middlewares = ( + async_buffered_gas_estimate_middleware, + async_default_transaction_fields_middleware, + async_ethereum_tester_middleware + ) + def __init__(self) -> None: - self.eth_tester = EthereumTesterProvider() + from eth_tester import EthereumTester + self.ethereum_tester = EthereumTester() + self.api_endpoints = API_ENDPOINTS - async def make_request( - self, method: RPCEndpoint, params: Any - ) -> RPCResponse: - return self.eth_tester.make_request(method, params) + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + namespace, _, endpoint = method.partition('_') + from eth_tester.exceptions import TransactionFailed + try: + delegator = self.api_endpoints[namespace][endpoint] + except KeyError: + return RPCResponse( + {"error": f"Unknown RPC Endpoint: {method}"} + ) + try: + response = delegator(self.ethereum_tester, params) + except NotImplementedError: + return RPCResponse( + {"error": f"RPC Endpoint has not been implemented: {method}"} + ) + except TransactionFailed as e: + try: + reason = decode_single('(string)', e.args[0].args[0][4:])[0] + except (InsufficientDataBytes, AttributeError): + reason = e.args[0] + raise TransactionFailed(f'execution reverted: {reason}') + else: + return { + 'result': response, + } + + async def isConnected(self) -> Literal[True]: + return True class EthereumTesterProvider(BaseProvider): diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 18fbd42965..eea53e14e6 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -39,7 +39,11 @@ from web3.middleware import ( construct_formatting_middleware, ) +from web3.middleware.formatting import ( + async_construct_formatting_middleware, +) from web3.types import ( + Middleware, RPCEndpoint, RPCResponse, TxParams, @@ -181,109 +185,120 @@ def is_hexstr(value: Any) -> bool: } receipt_result_formatter = apply_formatters_to_dict(RECEIPT_RESULT_FORMATTERS) +request_formatters = { + # Eth + RPCEndpoint('eth_getBlockByNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getFilterChanges'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getFilterLogs'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getUncleCountByBlockNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getTransactionByBlockHashAndIndex'): apply_formatters_to_args( + identity, + to_integer_if_hex, + ), + RPCEndpoint('eth_getTransactionByBlockNumberAndIndex'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + to_integer_if_hex, + ), + RPCEndpoint('eth_getUncleByBlockNumberAndIndex'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + to_integer_if_hex, + ), + RPCEndpoint('eth_newFilter'): apply_formatters_to_args( + filter_request_transformer, + ), + RPCEndpoint('eth_getLogs'): apply_formatters_to_args( + filter_request_transformer, + ), + RPCEndpoint('eth_sendTransaction'): apply_formatters_to_args( + transaction_request_transformer, + ), + RPCEndpoint('eth_estimateGas'): apply_formatters_to_args( + transaction_request_transformer, + ), + RPCEndpoint('eth_call'): apply_formatters_to_args( + transaction_request_transformer, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_uninstallFilter'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getCode'): apply_formatters_to_args( + identity, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getBalance'): apply_formatters_to_args( + identity, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + # EVM + RPCEndpoint('evm_revert'): apply_formatters_to_args(hex_to_integer), + # Personal + RPCEndpoint('personal_sendTransaction'): apply_formatters_to_args( + transaction_request_transformer, + identity, + ), +} +result_formatters = { + RPCEndpoint('eth_getBlockByHash'): apply_formatter_if( + is_dict, + block_result_remapper, + ), + RPCEndpoint('eth_getBlockByNumber'): apply_formatter_if( + is_dict, + block_result_remapper, + ), + RPCEndpoint('eth_getBlockTransactionCountByHash'): apply_formatter_if( + is_dict, + transaction_result_remapper, + ), + RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatter_if( + is_dict, + transaction_result_remapper, + ), + RPCEndpoint('eth_getTransactionByHash'): apply_formatter_if( + is_dict, + compose(transaction_result_remapper, transaction_result_formatter), + ), + RPCEndpoint('eth_getTransactionReceipt'): apply_formatter_if( + is_dict, + compose(receipt_result_remapper, receipt_result_formatter), + ), + RPCEndpoint('eth_newFilter'): integer_to_hex, + RPCEndpoint('eth_newBlockFilter'): integer_to_hex, + RPCEndpoint('eth_newPendingTransactionFilter'): integer_to_hex, + RPCEndpoint('eth_getLogs'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + RPCEndpoint('eth_getFilterChanges'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + RPCEndpoint('eth_getFilterLogs'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + # EVM + RPCEndpoint('evm_snapshot'): integer_to_hex, +} + + +async def async_ethereum_tester_middleware(make_request, web3: "Web3") -> Middleware: + middleware = await async_construct_formatting_middleware( + request_formatters=request_formatters, + result_formatters=result_formatters + ) + return await middleware(make_request, web3) + ethereum_tester_middleware = construct_formatting_middleware( - request_formatters={ - # Eth - RPCEndpoint('eth_getBlockByNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getFilterChanges'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getFilterLogs'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getUncleCountByBlockNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getTransactionByBlockHashAndIndex'): apply_formatters_to_args( - identity, - to_integer_if_hex, - ), - RPCEndpoint('eth_getTransactionByBlockNumberAndIndex'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - to_integer_if_hex, - ), - RPCEndpoint('eth_getUncleByBlockNumberAndIndex'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - to_integer_if_hex, - ), - RPCEndpoint('eth_newFilter'): apply_formatters_to_args( - filter_request_transformer, - ), - RPCEndpoint('eth_getLogs'): apply_formatters_to_args( - filter_request_transformer, - ), - RPCEndpoint('eth_sendTransaction'): apply_formatters_to_args( - transaction_request_transformer, - ), - RPCEndpoint('eth_estimateGas'): apply_formatters_to_args( - transaction_request_transformer, - ), - RPCEndpoint('eth_call'): apply_formatters_to_args( - transaction_request_transformer, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_uninstallFilter'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getCode'): apply_formatters_to_args( - identity, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getBalance'): apply_formatters_to_args( - identity, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - # EVM - RPCEndpoint('evm_revert'): apply_formatters_to_args(hex_to_integer), - # Personal - RPCEndpoint('personal_sendTransaction'): apply_formatters_to_args( - transaction_request_transformer, - identity, - ), - }, - result_formatters={ - RPCEndpoint('eth_getBlockByHash'): apply_formatter_if( - is_dict, - block_result_remapper, - ), - RPCEndpoint('eth_getBlockByNumber'): apply_formatter_if( - is_dict, - block_result_remapper, - ), - RPCEndpoint('eth_getBlockTransactionCountByHash'): apply_formatter_if( - is_dict, - transaction_result_remapper, - ), - RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatter_if( - is_dict, - transaction_result_remapper, - ), - RPCEndpoint('eth_getTransactionByHash'): apply_formatter_if( - is_dict, - compose(transaction_result_remapper, transaction_result_formatter), - ), - RPCEndpoint('eth_getTransactionReceipt'): apply_formatter_if( - is_dict, - compose(receipt_result_remapper, receipt_result_formatter), - ), - RPCEndpoint('eth_newFilter'): integer_to_hex, - RPCEndpoint('eth_newBlockFilter'): integer_to_hex, - RPCEndpoint('eth_newPendingTransactionFilter'): integer_to_hex, - RPCEndpoint('eth_getLogs'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - RPCEndpoint('eth_getFilterChanges'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - RPCEndpoint('eth_getFilterLogs'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - # EVM - RPCEndpoint('evm_snapshot'): integer_to_hex, - }, + request_formatters=request_formatters, + result_formatters=result_formatters ) @@ -313,6 +328,17 @@ def fill_default( return assoc(transaction, field, guess_val) +async def async_fill_default( + field: str, guess_func: Callable[..., Any], web3: "Web3", transaction: TxParams +) -> TxParams: + # type ignored b/c TxParams keys must be string literal types + if field in transaction and transaction[field] is not None: # type: ignore + return transaction + else: + guess_val = guess_func(web3, transaction) + return assoc(transaction, field, guess_val) + + def default_transaction_fields_middleware( make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: @@ -332,3 +358,19 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: else: return make_request(method, params) return middleware + + +async def async_default_transaction_fields_middleware( + make_request: Callable[[RPCEndpoint, Any], Any], web3: "Web3" +) -> Callable[[RPCEndpoint, Any], RPCResponse]: + async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: + if method in ( + 'eth_call', + 'eth_estimateGas', + 'eth_sendTransaction', + ): + filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) + return await make_request(method, [filled_transaction] + list(params)[1:]) # type: ignore + else: + return await make_request(method, params) + return middleware From 04bf7113119c8c578d35d38a8732f8d2e21c08f2 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 3 Mar 2022 21:33:17 -0500 Subject: [PATCH 02/39] more linting, still more to go --- web3/contract.py | 12 +++++++----- web3/providers/eth_tester/middleware.py | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 89765e2448..c3f26b310b 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -333,7 +333,7 @@ class BaseContract: bytecode_runtime = None clone_bin = None - functions: ContractFunctions = None + functions: Union[ContractFunctions, AsyncContractFunctions] = None caller: 'ContractCaller' = None #: Instance of :class:`ContractEvents` presenting available Event ABIs @@ -722,7 +722,7 @@ def estimateGas( estimate_gas_transaction, block_identifier=block_identifier ) - def _get_transaction(self, transaction: Optional[TxParams] = None) -> HexBytes: + def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: transact_transaction: TxParams = {} else: @@ -781,7 +781,8 @@ class AsyncContractConstructor(BaseContractConstructor): @combomethod async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: - return await self.w3.eth.send_transaction(self._get_transaction(transaction)) + return await self.w3.eth.send_transaction( # type: ignore + self._get_transaction(transaction)) class ConciseMethod: @@ -954,7 +955,7 @@ def __init__(self, abi: Optional[ABIFunction] = None) -> None: self.abi = abi self.fn_name = type(self).__name__ - def __call__(self, *args: Any, **kwargs: Any) -> 'ContractFunction': + def __call__(self, *args: Any, **kwargs: Any) -> 'BaseContractFunction': clone = copy.copy(self) if args is None: clone.args = tuple() @@ -1523,7 +1524,8 @@ def __init__(self, address: ChecksumAddress, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', - contract_function_class: Optional[BaseContractFunction] = ContractFunction + contract_function_class: + Optional[Union[ContractFunction, AsyncContractFunction]] = ContractFunction ) -> None: self.w3 = w3 self.address = address diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index eea53e14e6..9c7c0e64f6 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -370,7 +370,8 @@ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: 'eth_sendTransaction', ): filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) - return await make_request(method, [filled_transaction] + list(params)[1:]) # type: ignore + return await make_request(method, # type: ignore + [filled_transaction] + list(params)[1:]) else: return await make_request(method, params) return middleware From e4dfaced4fbb936a4b74f12a9035d8aae6df8852 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 4 Mar 2022 05:58:35 -0500 Subject: [PATCH 03/39] more linting. I seem to have broken some of the test on this one. --- tests/core/contracts/conftest.py | 4 -- web3/contract.py | 86 +++++++++++++++++-------- web3/providers/eth_tester/middleware.py | 2 +- 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 29d1fbd4e0..4ff34bcc3b 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -9,9 +9,6 @@ identity, ) -from ens import ( - AsyncENS, -) from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, @@ -1071,7 +1068,6 @@ async def async_w3(): provider = AsyncEthereumTesterProvider() w3 = Web3(provider, modules={'eth': [AsyncEth]}, middlewares=provider.middlewares) - w3.ens = AsyncENS.fromWeb3(w3) w3.eth.default_account = await w3.eth.coinbase return w3 diff --git a/web3/contract.py b/web3/contract.py index c3f26b310b..a4b3eb80a3 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -955,20 +955,6 @@ def __init__(self, abi: Optional[ABIFunction] = None) -> None: self.abi = abi self.fn_name = type(self).__name__ - def __call__(self, *args: Any, **kwargs: Any) -> 'BaseContractFunction': - clone = copy.copy(self) - if args is None: - clone.args = tuple() - else: - clone.args = args - - if kwargs is None: - clone.kwargs = {} - else: - clone.kwargs = kwargs - clone._set_function_info() - return clone - def _set_function_info(self) -> None: if not self.abi: self.abi = find_matching_fn_abi( @@ -1158,6 +1144,22 @@ def __repr__(self) -> str: class ContractFunction(BaseContractFunction): + def __call__(self, + *args: Any, + **kwargs: Any) -> 'ContractFunction': + clone = copy.copy(self) + if args is None: + clone.args = tuple() + else: + clone.args = args + + if kwargs is None: + clone.kwargs = {} + else: + clone.kwargs = kwargs + clone._set_function_info() + return clone + def call(self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', state_override: Optional[CallOverrideParams] = None, @@ -1206,6 +1208,22 @@ def call(self, transaction: Optional[TxParams] = None, class AsyncContractFunction(BaseContractFunction): + def __call__(self, + *args: Any, + **kwargs: Any) -> 'AsyncContractFunction': + clone = copy.copy(self) + if args is None: + clone.args = tuple() + else: + clone.args = args + + if kwargs is None: + clone.kwargs = {} + else: + clone.kwargs = kwargs + clone._set_function_info() + return clone + async def call( self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', @@ -1525,7 +1543,8 @@ def __init__(self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', contract_function_class: - Optional[Union[ContractFunction, AsyncContractFunction]] = ContractFunction + Optional[Union[Type[ContractFunction], + Type[AsyncContractFunction]]] = ContractFunction ) -> None: self.w3 = w3 self.address = address @@ -1580,17 +1599,6 @@ def __hasattr__(self, event_name: str) -> bool: except ABIFunctionNotFound: return False - def __call__( - self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest' - ) -> 'ContractCaller': - if transaction is None: - transaction = {} - return type(self)(self.abi, - self.w3, - self.address, - transaction=transaction, - block_identifier=block_identifier) - @staticmethod def call_function( fn: ContractFunction, @@ -1614,6 +1622,17 @@ def __init__(self, super().__init__(abi, w3, address, transaction, block_identifier, ContractFunction) + def __call__( + self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest' + ) -> 'ContractCaller': + if transaction is None: + transaction = {} + return type(self)(self.abi, + self.w3, + self.address, + transaction=transaction, + block_identifier=block_identifier) + class AsyncContractCaller(BaseContractCaller): @@ -1626,6 +1645,17 @@ def __init__(self, super().__init__(abi, w3, address, transaction, block_identifier, AsyncContractFunction) + def __call__( + self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest' + ) -> 'AsyncContractCaller': + if transaction is None: + transaction = {} + return type(self)(self.abi, + self.w3, + self.address, + transaction=transaction, + block_identifier=block_identifier) + def check_for_forbidden_api_filter_arguments( event_abi: ABIEvent, _filters: Dict[str, Any] @@ -1766,7 +1796,7 @@ async def async_call_contract_function( fn_kwargs=kwargs, ) - return_data = await w3.eth.call( + return_data = await w3.eth.call( # type: ignore call_transaction, block_identifier=block_id, state_override=state_override, @@ -1802,7 +1832,7 @@ async def async_parse_block_identifier(w3: 'Web3', elif block_identifier in ['latest', 'earliest', 'pending']: return block_identifier elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier): - return await w3.eth.get_block(block_identifier)['number'] + return await w3.eth.get_block(block_identifier)['number'] # type: ignore else: raise BlockNumberOutofRange diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 9c7c0e64f6..23aa8db7c3 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -370,7 +370,7 @@ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: 'eth_sendTransaction', ): filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) - return await make_request(method, # type: ignore + return await make_request(method, [filled_transaction] + list(params)[1:]) else: return await make_request(method, params) From acdae388f8d3f70e99aa2481ebdc7b5851324848 Mon Sep 17 00:00:00 2001 From: DB Date: Sun, 6 Mar 2022 07:22:13 -0500 Subject: [PATCH 04/39] fixed broken test --- web3/contract.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index a4b3eb80a3..24f61a54af 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -168,16 +168,16 @@ def __init__(self, if self.abi: self._functions = filter_by_type('function', self.abi) - for func in self._functions: - setattr( - self, - func['name'], - contract_function_class.factory( + for func in self._functions: + setattr( + self, func['name'], - w3=self.w3, - contract_abi=self.abi, - address=self.address, - function_identifier=func['name'])) + contract_function_class.factory( + func['name'], + w3=self.w3, + contract_abi=self.abi, + address=self.address, + function_identifier=func['name'])) def __iter__(self) -> Generator[str, None, None]: if not hasattr(self, '_functions') or not self._functions: From 3c636963a2ec4c2952b6ca97b7c4269ed4bd64b1 Mon Sep 17 00:00:00 2001 From: DB Date: Sun, 6 Mar 2022 19:30:04 -0500 Subject: [PATCH 05/39] This is most of the linting changes so far --- web3/contract.py | 137 +++++++++++++++--------- web3/providers/eth_tester/middleware.py | 7 +- 2 files changed, 90 insertions(+), 54 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 24f61a54af..4c9cf731c5 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -333,9 +333,6 @@ class BaseContract: bytecode_runtime = None clone_bin = None - functions: Union[ContractFunctions, AsyncContractFunctions] = None - caller: 'ContractCaller' = None - #: Instance of :class:`ContractEvents` presenting available Event ABIs events: ContractEvents = None @@ -352,22 +349,7 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: :param address: Contract address as 0x hex string""" - self.caller = ContractCaller(self.abi, self.w3, self.address) self.events = ContractEvents(self.abi, self.w3, self.address) - self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) - self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) - - @classmethod - def factory(cls, - contract: Union['Contract', 'AsyncContract'] - ) -> Union['Contract', 'AsyncContract']: - - contract.caller = ContractCaller(contract.abi, contract.w3, contract.address) - contract.events = ContractEvents(contract.abi, contract.w3) - contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3) - contract.receive = Contract.get_receive_function(contract.abi, contract.w3) - - return contract # # Contract Methods @@ -526,34 +508,6 @@ def _find_matching_event_abi( event_name=event_name, argument_names=argument_names) - @staticmethod - def get_fallback_function( - abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None - ) -> 'ContractFunction': - if abi and fallback_func_abi_exists(abi): - return ContractFunction.factory( - 'fallback', - w3=w3, - contract_abi=abi, - address=address, - function_identifier=FallbackFn)() - - return cast('ContractFunction', NonExistentFallbackFunction()) - - @staticmethod - def get_receive_function( - abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None - ) -> 'ContractFunction': - if abi and receive_func_abi_exists(abi): - return ContractFunction.factory( - 'receive', - w3=w3, - contract_abi=abi, - address=address, - function_identifier=ReceiveFn)() - - return cast('ContractFunction', NonExistentReceiveFunction()) - @combomethod def _encode_constructor_data(cls, args: Optional[Any] = None, kwargs: Optional[Any] = None) -> HexStr: @@ -582,6 +536,9 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, class Contract(BaseContract): + functions: ContractFunctions = None + caller: 'ContractCaller' = None + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: if self.w3 is None: raise AttributeError( @@ -596,6 +553,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: raise TypeError("The address argument is required to instantiate a contract.") self.functions = ContractFunctions(self.abi, self.w3, self.address) + self.caller = ContractCaller(self.abi, self.w3, self.address) + self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) + self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) super().__init__(address) @classmethod @@ -616,12 +576,47 @@ def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> normalizers=normalizers, )) contract.functions = ContractFunctions(contract.abi, contract.w3) - contract = BaseContract.factory(contract) + contract.caller = ContractCaller(contract.abi, contract.w3, contract.address) + contract.events = ContractEvents(contract.abi, contract.w3) + contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3) + contract.receive = Contract.get_receive_function(contract.abi, contract.w3) + return contract + @staticmethod + def get_fallback_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'ContractFunction': + if abi and fallback_func_abi_exists(abi): + return ContractFunction.factory( + 'fallback', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=FallbackFn)() + + return cast('ContractFunction', NonExistentFallbackFunction()) + + @staticmethod + def get_receive_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'ContractFunction': + if abi and receive_func_abi_exists(abi): + return ContractFunction.factory( + 'receive', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=ReceiveFn)() + + return cast('ContractFunction', NonExistentReceiveFunction()) + class AsyncContract(BaseContract): + functions: AsyncContractFunctions = None + caller: 'AsyncContractCaller' = None + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: if self.w3 is None: raise AttributeError( @@ -635,6 +630,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: if not self.address: raise TypeError("The address argument is required to instantiate a contract.") self.functions = AsyncContractFunctions(self.abi, self.w3, self.address) + self.caller = AsyncContractCaller(self.abi, self.w3, self.address) + self.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, self.address) + self.receive = AsyncContract.get_receive_function(self.abi, self.w3, self.address) super().__init__(address) @classmethod @@ -657,9 +655,40 @@ def factory(cls, w3: 'Web3', normalizers=normalizers, )) contract.functions = AsyncContractFunctions(contract.abi, contract.w3) - contract = BaseContract.factory(contract) + contract.caller = AsyncContractCaller(contract.abi, contract.w3, contract.address) + contract.events = ContractEvents(contract.abi, contract.w3) + contract.fallback = AsyncContract.get_fallback_function(contract.abi, contract.w3) + contract.receive = AsyncContract.get_receive_function(contract.abi, contract.w3) return contract + @staticmethod + def get_fallback_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'AsyncContractFunction': + if abi and fallback_func_abi_exists(abi): + return AsyncContractFunction.factory( + 'fallback', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=FallbackFn)() + + return cast('AsyncContractFunction', NonExistentFallbackFunction()) + + @staticmethod + def get_receive_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'AsyncContractFunction': + if abi and receive_func_abi_exists(abi): + return AsyncContractFunction.factory( + 'receive', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=ReceiveFn)() + + return cast('AsyncContractFunction', NonExistentReceiveFunction()) + def mk_collision_prop(fn_name: str) -> Callable[[], None]: def collision_fn() -> NoReturn: @@ -1129,10 +1158,6 @@ def _encode_transaction_data(cls) -> HexStr: _return_data_normalizers: Optional[Tuple[Callable[..., Any], ...]] = tuple() - @classmethod - def factory(cls, class_name: str, **kwargs: Any) -> 'ContractFunction': - return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) - def __repr__(self) -> str: if self.abi: _repr = ' 'ContractFunction': + return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + class AsyncContractFunction(BaseContractFunction): @@ -1271,6 +1300,10 @@ async def call( **self.kwargs ) + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> 'AsyncContractFunction': + return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + class ContractEvent: """Base class for contract events diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 23aa8db7c3..76216dd864 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -3,6 +3,8 @@ TYPE_CHECKING, Any, Callable, + Dict, + Optional, ) from eth_typing import ( @@ -243,7 +245,7 @@ def is_hexstr(value: Any) -> bool: identity, ), } -result_formatters = { +result_formatters: Optional[Dict[RPCEndpoint, Callable[..., Any]]] = { RPCEndpoint('eth_getBlockByHash'): apply_formatter_if( is_dict, block_result_remapper, @@ -288,7 +290,8 @@ def is_hexstr(value: Any) -> bool: } -async def async_ethereum_tester_middleware(make_request, web3: "Web3") -> Middleware: +async def async_ethereum_tester_middleware(make_request, web3: "Web3" # type: ignore + ) -> Middleware: middleware = await async_construct_formatting_middleware( request_formatters=request_formatters, result_formatters=result_formatters From 7118d2487e586c802ee2d876868983bb5d9e933a Mon Sep 17 00:00:00 2001 From: DB Date: Sun, 6 Mar 2022 20:32:32 -0500 Subject: [PATCH 06/39] async test --- tests/core/contracts/conftest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 4ff34bcc3b..c0410fb0b5 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1,6 +1,7 @@ import functools import json import pytest +import pytest_asyncio from eth_utils import ( event_signature_to_log_topic, @@ -1051,6 +1052,7 @@ def buildTransaction(request): return functools.partial(invoke_contract, api_call_desig='buildTransaction') +@pytest_asyncio.fixture() async def async_deploy(web3, Contract, apply_func=identity, args=None): args = args or [] deploy_txn = await Contract.constructor(*args).transact() @@ -1063,7 +1065,7 @@ async def async_deploy(web3, Contract, apply_func=identity, args=None): return contract -@pytest.fixture() +@pytest_asyncio.fixture() async def async_w3(): provider = AsyncEthereumTesterProvider() w3 = Web3(provider, modules={'eth': [AsyncEth]}, @@ -1072,7 +1074,7 @@ async def async_w3(): return w3 -@pytest.fixture() +@pytest_asyncio.fixture() def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): contract = AsyncContract.factory(async_w3, abi=MATH_ABI, @@ -1081,6 +1083,6 @@ def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): return contract -@pytest.fixture() +@pytest_asyncio.fixture() async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) From e15971e9c8ec1e093fea598d6551bb48828039ef Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 7 Mar 2022 05:55:27 -0500 Subject: [PATCH 07/39] lint rearrange imports --- tests/core/contracts/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index c0410fb0b5..e400d4d05b 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1,7 +1,7 @@ import functools import json import pytest -import pytest_asyncio + from eth_utils import ( event_signature_to_log_topic, @@ -9,6 +9,7 @@ from eth_utils.toolz import ( identity, ) +import pytest_asyncio from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( From 3682aa31e046324186c6ee0ab5868892607523a5 Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 7 Mar 2022 06:01:27 -0500 Subject: [PATCH 08/39] ContractEvents/ContractEvent Async --- web3/contract.py | 190 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 148 insertions(+), 42 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 4c9cf731c5..d1b474d81c 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -232,7 +232,7 @@ def __init__(self, super().__init__(abi, w3, AsyncContractFunction, address) -class ContractEvents: +class BaseContractEvents: """Class containing contract event objects This is available via: @@ -253,7 +253,9 @@ class ContractEvents: """ - def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None) -> None: + def __init__(self, abi: ABI, w3: 'Web3', + contract_event_type: Union[Type['ContractEvent'], Type['AsyncContractEvent']], + address: Optional[ChecksumAddress] = None) -> None: if abi: self.abi = abi self._events = filter_by_type('event', self.abi) @@ -261,7 +263,7 @@ def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = No setattr( self, event['name'], - ContractEvent.factory( + contract_event_type.factory( event['name'], w3=w3, contract_abi=self.abi, @@ -300,6 +302,20 @@ def __hasattr__(self, event_name: str) -> bool: return False +class ContractEvents(BaseContractEvents): + + def __init__(self, abi: ABI, w3: 'Web3', + address: Optional[ChecksumAddress] = None) -> None: + super().__init__(abi, w3, ContractEvent, address) + + +class AsyncContractEvents(BaseContractEvents): + + def __init__(self, abi: ABI, w3: 'Web3', + address: Optional[ChecksumAddress] = None) -> None: + super().__init__(abi, w3, AsyncContractEvent, address) + + class BaseContract: """Base class for Contract proxy classes. @@ -344,13 +360,6 @@ class BaseContract: src_map_runtime = None user_doc = None - def __init__(self, address: Optional[ChecksumAddress] = None) -> None: - """Create a new smart contract proxy object. - - :param address: Contract address as 0x hex string""" - - self.events = ContractEvents(self.abi, self.w3, self.address) - # # Contract Methods # @@ -540,6 +549,9 @@ class Contract(BaseContract): caller: 'ContractCaller' = None def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + """Create a new smart contract proxy object. + + :param address: Contract address as 0x hex string""" if self.w3 is None: raise AttributeError( 'The `Contract` class has not been initialized. Please use the ' @@ -556,6 +568,7 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.caller = ContractCaller(self.abi, self.w3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) + self.events = ContractEvents(self.abi, self.w3, self.address) super().__init__(address) @classmethod @@ -618,6 +631,9 @@ class AsyncContract(BaseContract): caller: 'AsyncContractCaller' = None def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + """Create a new smart contract proxy object. + + :param address: Contract address as 0x hex string""" if self.w3 is None: raise AttributeError( 'The `Contract` class has not been initialized. Please use the ' @@ -633,6 +649,7 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.caller = AsyncContractCaller(self.abi, self.w3, self.address) self.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, self.address) self.receive = AsyncContract.get_receive_function(self.abi, self.w3, self.address) + self.events = AsyncContractEvents(self.abi, self.w3, self.address) super().__init__(address) @classmethod @@ -1305,7 +1322,7 @@ def factory(cls, class_name: str, **kwargs: Any) -> 'AsyncContractFunction': return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) -class ContractEvent: +class BaseContractEvent: """Base class for contract events An event accessed via the api contract.events.myEvents(*args, **kwargs) @@ -1442,6 +1459,54 @@ def build_filter(self) -> EventFilterBuilder: builder.address = self.address return builder + @combomethod + def _get_event_filter_params(self, + abi: ABIEvent, + argument_filters: Optional[Dict[str, Any]] = None, + fromBlock: Optional[BlockIdentifier] = None, + toBlock: Optional[BlockIdentifier] = None, + blockHash: Optional[HexBytes] = None) -> Iterable[EventData]: + + if not self.address: + raise TypeError("This method can be only called on " + "an instated contract with an address") + + 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 + data_filter_set, event_filter_params = construct_event_filter_params( + abi, + self.w3.codec, + contract_address=self.address, + argument_filters=_filters, + fromBlock=fromBlock, + toBlock=toBlock, + address=self.address, + ) + + if blockHash is not None: + event_filter_params['blockHash'] = blockHash + + return event_filter_params + + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: + return PropertyCheckingFactory(class_name, (cls,), kwargs) + + +class ContractEvent(BaseContractEvent): + @combomethod def getLogs(self, argument_filters: Optional[Dict[str, Any]] = None, @@ -1502,50 +1567,91 @@ def getLogs(self, same time as fromBlock or toBlock :yield: Tuple of :class:`AttributeDict` instances """ + abi = self._get_event_abi() + # Call JSON-RPC API + logs = self.w3.eth.get_logs(self._get_event_filter_params(abi, + argument_filters, + fromBlock, + toBlock, + blockHash)) - if not self.address: - raise TypeError("This method can be only called on " - "an instated contract with an address") + # Convert raw binary data to Python proxy objects as described by ABI + return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) - abi = self._get_event_abi() - if argument_filters is None: - argument_filters = dict() +class AsyncContractEvent(BaseContractEvent): - _filters = dict(**argument_filters) + @combomethod + async def getLogs(self, + argument_filters: Optional[Dict[str, Any]] = None, + fromBlock: Optional[BlockIdentifier] = None, + toBlock: Optional[BlockIdentifier] = None, + blockHash: Optional[HexBytes] = None) -> Iterable[EventData]: + """Get events for this contract instance using eth_getLogs API. - 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') + 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. - # Construct JSON-RPC raw filter presentation based on human readable Python descriptions - # Namely, convert event names to their keccak signatures - data_filter_set, event_filter_params = construct_event_filter_params( - abi, - self.w3.codec, - contract_address=self.address, - argument_filters=_filters, - fromBlock=fromBlock, - toBlock=toBlock, - address=self.address, - ) + 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. - if blockHash is not None: - event_filter_params['blockHash'] = blockHash + Example - how to get all ERC-20 token transactions + for the latest 10 blocks: + + .. code-block:: python + + from = max(mycontract.web3.eth.block_number - 10, 1) + to = mycontract.web3.eth.block_number + + 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 + """ + abi = self._get_event_abi() # Call JSON-RPC API - logs = self.w3.eth.get_logs(event_filter_params) + logs = await self.w3.eth.get_logs(self._get_event_filter_params(abi, + argument_filters, + fromBlock, + toBlock, + blockHash)) # Convert raw binary data to Python proxy objects as described by ABI return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) - @classmethod - def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: - return PropertyCheckingFactory(class_name, (cls,), kwargs) - class BaseContractCaller: """ From b74662e634baf5d18d343322fcfe8d6c0a2b9ce7 Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 7 Mar 2022 06:04:46 -0500 Subject: [PATCH 09/39] couple more fixes --- tests/core/contracts/conftest.py | 1 - web3/contract.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index e400d4d05b..e066bd4f3e 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -2,7 +2,6 @@ import json import pytest - from eth_utils import ( event_signature_to_log_topic, ) diff --git a/web3/contract.py b/web3/contract.py index d1b474d81c..712d7d374e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -569,7 +569,6 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) self.events = ContractEvents(self.abi, self.w3, self.address) - super().__init__(address) @classmethod def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': @@ -650,7 +649,6 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, self.address) self.receive = AsyncContract.get_receive_function(self.abi, self.w3, self.address) self.events = AsyncContractEvents(self.abi, self.w3, self.address) - super().__init__(address) @classmethod def factory(cls, w3: 'Web3', From 2944ee966f4c58d5e88b65d35f45329b60258591 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 8 Mar 2022 05:57:54 -0500 Subject: [PATCH 10/39] more work on contract --- web3/contract.py | 88 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 712d7d374e..3efedb48f7 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -6,6 +6,7 @@ from typing import ( TYPE_CHECKING, Any, + Awaitable, Callable, Collection, Dict, @@ -139,6 +140,7 @@ BlockIdentifier, CallOverrideParams, EventData, + FilterParams, FunctionIdentifier, LogReceipt, TxParams, @@ -349,9 +351,6 @@ class BaseContract: bytecode_runtime = None clone_bin = None - #: Instance of :class:`ContractEvents` presenting available Event ABIs - events: ContractEvents = None - dev_doc = None interface = None metadata = None @@ -403,13 +402,14 @@ def encodeABI(cls, fn_name: str, args: Optional[Any] = None, return encode_abi(cls.w3, fn_abi, fn_arguments, data) @combomethod - def all_functions(self) -> List['ContractFunction']: - return find_functions_by_identifier( + def all_functions(self) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: + return self.find_functions_by_identifier( self.abi, self.w3, self.address, lambda _: True ) @combomethod - def get_function_by_signature(self, signature: str) -> 'ContractFunction': + def get_function_by_signature(self, signature: str + ) -> Union['ContractFunction', 'AsyncContractFunction']: if ' ' in signature: raise ValueError( 'Function signature should not contain any spaces. ' @@ -419,15 +419,16 @@ def get_function_by_signature(self, signature: str) -> 'ContractFunction': def callable_check(fn_abi: ABIFunction) -> bool: return abi_to_signature(fn_abi) == signature - fns = find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) + fns = self.find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) return get_function_by_identifier(fns, 'signature') @combomethod - def find_functions_by_name(self, fn_name: str) -> List['ContractFunction']: + def find_functions_by_name(self, fn_name: str + ) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: def callable_check(fn_abi: ABIFunction) -> bool: return fn_abi['name'] == fn_name - return find_functions_by_identifier( + return self.find_functions_by_identifier( self.abi, self.w3, self.address, callable_check ) @@ -437,13 +438,14 @@ def get_function_by_name(self, fn_name: str) -> 'ContractFunction': return get_function_by_identifier(fns, 'name') @combomethod - def get_function_by_selector(self, selector: Union[bytes, int, HexStr]) -> 'ContractFunction': + def get_function_by_selector(self, selector: Union[bytes, int, HexStr] + ) -> Union['ContractFunction', 'AsyncContractFunction']: def callable_check(fn_abi: ABIFunction) -> bool: # typed dict cannot be used w/ a normal Dict # https://github.com/python/mypy/issues/4976 return encode_hex(function_abi_to_4byte_selector(fn_abi)) == to_4byte_hex(selector) # type: ignore # noqa: E501 - fns = find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) + fns = self.find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) return get_function_by_identifier(fns, 'selector') @combomethod @@ -462,18 +464,19 @@ def decode_function_input(self, data: HexStr) -> Tuple['ContractFunction', Dict[ return func, dict(zip(names, normalized)) @combomethod - def find_functions_by_args(self, *args: Any) -> List['ContractFunction']: + def find_functions_by_args(self, *args: Any + ) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: def callable_check(fn_abi: ABIFunction) -> bool: return check_if_arguments_can_be_encoded(fn_abi, self.w3.codec, args=args, kwargs={}) - return find_functions_by_identifier( + return self.find_functions_by_identifier( self.abi, self.w3, self.address, callable_check ) @combomethod def get_function_by_args(self, *args: Any) -> 'ContractFunction': fns = self.find_functions_by_args(*args) - return get_function_by_identifier(fns, 'args') + return self.get_function_by_identifier(fns, 'args') # # Private Helpers @@ -542,12 +545,24 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, return deploy_data + @classmethod + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List[Any]: + pass + class Contract(BaseContract): functions: ContractFunctions = None caller: 'ContractCaller' = None + #: Instance of :class:`ContractEvents` presenting available Event ABIs + events: ContractEvents = None + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: """Create a new smart contract proxy object. @@ -566,9 +581,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.functions = ContractFunctions(self.abi, self.w3, self.address) self.caller = ContractCaller(self.abi, self.w3, self.address) + self.events = ContractEvents(self.abi, self.w3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) - self.events = ContractEvents(self.abi, self.w3, self.address) @classmethod def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': @@ -623,12 +638,23 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List['ContractFunction']: + return find_functions_by_identifier(contract_abi, w3, address, callable_check) + class AsyncContract(BaseContract): functions: AsyncContractFunctions = None caller: 'AsyncContractCaller' = None + #: Instance of :class:`ContractEvents` presenting available Event ABIs + events: AsyncContractEvents = None + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: """Create a new smart contract proxy object. @@ -646,9 +672,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: raise TypeError("The address argument is required to instantiate a contract.") self.functions = AsyncContractFunctions(self.abi, self.w3, self.address) self.caller = AsyncContractCaller(self.abi, self.w3, self.address) + self.events = AsyncContractEvents(self.abi, self.w3, self.address) self.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, self.address) self.receive = AsyncContract.get_receive_function(self.abi, self.w3, self.address) - self.events = AsyncContractEvents(self.abi, self.w3, self.address) @classmethod def factory(cls, w3: 'Web3', @@ -704,6 +730,14 @@ def get_receive_function( return cast('AsyncContractFunction', NonExistentReceiveFunction()) + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List['ContractFunction']: + return async_find_functions_by_identifier(contract_abi, w3, address, callable_check) + def mk_collision_prop(fn_name: str) -> Callable[[], None]: def collision_fn() -> NoReturn: @@ -1463,7 +1497,7 @@ def _get_event_filter_params(self, argument_filters: Optional[Dict[str, Any]] = None, fromBlock: Optional[BlockIdentifier] = None, toBlock: Optional[BlockIdentifier] = None, - blockHash: Optional[HexBytes] = None) -> Iterable[EventData]: + blockHash: Optional[HexBytes] = None) -> FilterParams: if not self.address: raise TypeError("This method can be only called on " @@ -1584,7 +1618,7 @@ async def getLogs(self, argument_filters: Optional[Dict[str, Any]] = None, fromBlock: Optional[BlockIdentifier] = None, toBlock: Optional[BlockIdentifier] = None, - blockHash: Optional[HexBytes] = None) -> Iterable[EventData]: + blockHash: Optional[HexBytes] = None) -> Awaitable[Iterable[EventData]]: """Get events for this contract instance using eth_getLogs API. This is a stateless method, as opposed to createFilter. @@ -2090,6 +2124,24 @@ def find_functions_by_identifier( ] +def async_find_functions_by_identifier( + contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] +) -> List[AsyncContractFunction]: + fns_abi = filter_by_type('function', contract_abi) + return [ + AsyncContractFunction.factory( + fn_abi['name'], + w3=w3, + contract_abi=contract_abi, + address=address, + function_identifier=fn_abi['name'], + abi=fn_abi + ) + for fn_abi in fns_abi + if callable_check(fn_abi) + ] + + def get_function_by_identifier( fns: Sequence[ContractFunction], identifier: str ) -> ContractFunction: From 6473a54865a00e2bf777460309dae98da7ea41d2 Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Tue, 8 Mar 2022 15:50:04 -0700 Subject: [PATCH 11/39] add non-ens normalize_address func (#2384) --- web3/_utils/normalizers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web3/_utils/normalizers.py b/web3/_utils/normalizers.py index fe0b07dd7d..23667e13e3 100644 --- a/web3/_utils/normalizers.py +++ b/web3/_utils/normalizers.py @@ -253,6 +253,12 @@ def normalize_address(ens: ENS, address: ChecksumAddress) -> ChecksumAddress: return address +def normalize_address_no_ens(address: ChecksumAddress) -> ChecksumAddress: + if address: + validate_address(address) + return address + + def normalize_bytecode(bytecode: bytes) -> HexBytes: if bytecode: bytecode = HexBytes(bytecode) From d568a5e084dcb0ee3ef1d62ef47d8eb1f949ab79 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 8 Mar 2022 22:28:51 -0500 Subject: [PATCH 12/39] continued work on contract and fixed linting --- web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 91 ++++++++++++++--------- web3/providers/eth_tester/middleware.py | 2 +- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index 73abb9cbfb..fbe562bff4 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): # type: ignore + with pytest.raises(expected): w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index 3efedb48f7..f6ceef5a70 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -103,6 +103,7 @@ BASE_RETURN_NORMALIZERS, normalize_abi, normalize_address, + normalize_address_no_ens, normalize_bytecode, ) from web3._utils.transactions import ( @@ -359,28 +360,6 @@ class BaseContract: src_map_runtime = None user_doc = None - # - # Contract Methods - # - @classmethod - def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': - """ - :param args: The contract constructor arguments as positional arguments - :param kwargs: The contract constructor arguments as keyword arguments - :return: a contract constructor object - """ - if cls.bytecode is None: - raise ValueError( - "Cannot call constructor on a contract that does not have 'bytecode' associated " - "with it" - ) - - return ContractConstructor(cls.w3, - cls.abi, - cls.bytecode, - *args, - **kwargs) - # Public API # @combomethod @@ -433,7 +412,8 @@ def callable_check(fn_abi: ABIFunction) -> bool: ) @combomethod - def get_function_by_name(self, fn_name: str) -> 'ContractFunction': + def get_function_by_name(self, fn_name: str + ) -> Union['ContractFunction', 'AsyncContractFunction']: fns = self.find_functions_by_name(fn_name) return get_function_by_identifier(fns, 'name') @@ -449,7 +429,9 @@ def callable_check(fn_abi: ABIFunction) -> bool: return get_function_by_identifier(fns, 'selector') @combomethod - def decode_function_input(self, data: HexStr) -> Tuple['ContractFunction', Dict[str, Any]]: + def decode_function_input(self, data: HexStr + ) -> Union[Tuple['ContractFunction', Dict[str, Any]], + Tuple['AsyncContractFunction', Dict[str, Any]]]: # type ignored b/c expects data arg to be HexBytes data = HexBytes(data) # type: ignore selector, params = data[:4], data[4:] @@ -474,9 +456,10 @@ def callable_check(fn_abi: ABIFunction) -> bool: ) @combomethod - def get_function_by_args(self, *args: Any) -> 'ContractFunction': + def get_function_by_args(self, *args: Any + ) -> Union['ContractFunction', 'AsyncContractFunction']: fns = self.find_functions_by_args(*args) - return self.get_function_by_identifier(fns, 'args') + return get_function_by_identifier(fns, 'args') # # Private Helpers @@ -552,7 +535,7 @@ def find_functions_by_identifier(cls, address: ChecksumAddress, callable_check: Callable[..., Any] ) -> List[Any]: - pass + raise NotImplementedError("This method should be implemented in the inherited class") class Contract(BaseContract): @@ -610,6 +593,25 @@ def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> return contract + @classmethod + def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': + """ + :param args: The contract constructor arguments as positional arguments + :param kwargs: The contract constructor arguments as keyword arguments + :return: a contract constructor object + """ + if cls.bytecode is None: + raise ValueError( + "Cannot call constructor on a contract that does not have 'bytecode' associated " + "with it" + ) + + return ContractConstructor(cls.w3, + cls.abi, + cls.bytecode, + *args, + **kwargs) + @staticmethod def get_fallback_function( abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None @@ -638,7 +640,7 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(cls, + def find_functions_by_identifier(cls, # type: ignore contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -666,7 +668,7 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: ) if address: - self.address = normalize_address(self.w3.ens, address) + self.address = normalize_address_no_ens(address) if not self.address: raise TypeError("The address argument is required to instantiate a contract.") @@ -684,7 +686,7 @@ def factory(cls, w3: 'Web3', normalizers = { 'abi': normalize_abi, - 'address': partial(normalize_address, kwargs['w3'].ens), + 'address': normalize_address_no_ens, 'bytecode': normalize_bytecode, 'bytecode_runtime': normalize_bytecode, } @@ -697,11 +699,30 @@ def factory(cls, w3: 'Web3', )) contract.functions = AsyncContractFunctions(contract.abi, contract.w3) contract.caller = AsyncContractCaller(contract.abi, contract.w3, contract.address) - contract.events = ContractEvents(contract.abi, contract.w3) + contract.events = AsyncContractEvents(contract.abi, contract.w3) contract.fallback = AsyncContract.get_fallback_function(contract.abi, contract.w3) contract.receive = AsyncContract.get_receive_function(contract.abi, contract.w3) return contract + @classmethod + def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': + """ + :param args: The contract constructor arguments as positional arguments + :param kwargs: The contract constructor arguments as keyword arguments + :return: a contract constructor object + """ + if cls.bytecode is None: + raise ValueError( + "Cannot call constructor on a contract that does not have 'bytecode' associated " + "with it" + ) + + return AsyncContractConstructor(cls.w3, + cls.abi, + cls.bytecode, + *args, + **kwargs) + @staticmethod def get_fallback_function( abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None @@ -730,12 +751,12 @@ def get_receive_function( return cast('AsyncContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(cls, + def find_functions_by_identifier(cls, # type: ignore contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] - ) -> List['ContractFunction']: + ) -> List['AsyncContractFunction']: return async_find_functions_by_identifier(contract_abi, w3, address, callable_check) @@ -1675,14 +1696,14 @@ async def getLogs(self, """ abi = self._get_event_abi() # Call JSON-RPC API - logs = await self.w3.eth.get_logs(self._get_event_filter_params(abi, + logs = await self.w3.eth.get_logs(self._get_event_filter_params(abi, # type: ignore argument_filters, fromBlock, toBlock, blockHash)) # Convert raw binary data to Python proxy objects as described by ABI - return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) + return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) # type: ignore class BaseContractCaller: diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 76216dd864..e230129af4 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -377,4 +377,4 @@ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: [filled_transaction] + list(params)[1:]) else: return await make_request(method, params) - return middleware + return middleware # type: ignore From 31c1e3423d5b653d10946bacf172a31aa5b245ff Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 9 Mar 2022 05:33:18 -0500 Subject: [PATCH 13/39] fixes --- web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index fbe562bff4..73abb9cbfb 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): + with pytest.raises(expected): # type: ignore w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index f6ceef5a70..23bee94528 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -528,8 +528,7 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, return deploy_data - @classmethod - def find_functions_by_identifier(cls, + def find_functions_by_identifier(self, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -640,7 +639,7 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(cls, # type: ignore + def find_functions_by_identifier(self, # type: ignore contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -751,7 +750,7 @@ def get_receive_function( return cast('AsyncContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(cls, # type: ignore + def find_functions_by_identifier(self, # type: ignore contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, From 3c2e26b3be29a82e1b790181b3ac60312072834e Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 9 Mar 2022 05:39:23 -0500 Subject: [PATCH 14/39] linting --- web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index 73abb9cbfb..fbe562bff4 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): # type: ignore + with pytest.raises(expected): w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index 23bee94528..3075fff074 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -639,7 +639,7 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(self, # type: ignore + def find_functions_by_identifier(self, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -750,7 +750,7 @@ def get_receive_function( return cast('AsyncContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(self, # type: ignore + def find_functions_by_identifier(self, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, From 722c7117b7ebb607c025983a0115b2cf5575b33e Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 9 Mar 2022 05:45:51 -0500 Subject: [PATCH 15/39] fix test import in the wrong place --- web3/providers/eth_tester/main.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index 987b6e0000..ffb20bd2e1 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -26,9 +26,6 @@ from web3.providers.async_base import ( AsyncBaseProvider, ) -from web3.providers.eth_tester.defaults import ( - API_ENDPOINTS, -) from web3.types import ( RPCEndpoint, RPCResponse, @@ -59,6 +56,7 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): def __init__(self) -> None: from eth_tester import EthereumTester + from web3.providers.eth_tester.defaults import API_ENDPOINTS self.ethereum_tester = EthereumTester() self.api_endpoints = API_ENDPOINTS From 79f99dd3065094ab1ca4b4d57555eb246e4426ed Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 9 Mar 2022 05:57:55 -0500 Subject: [PATCH 16/39] fixing combomethod --- web3/contract.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 3075fff074..9d661ca02d 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -528,7 +528,8 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, return deploy_data - def find_functions_by_identifier(self, + @combomethod + def find_functions_by_identifier(cls, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -639,7 +640,8 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(self, + @combomethod + def find_functions_by_identifier(cls, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, @@ -750,7 +752,8 @@ def get_receive_function( return cast('AsyncContractFunction', NonExistentReceiveFunction()) - def find_functions_by_identifier(self, + @combomethod + def find_functions_by_identifier(cls, contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, From c7bbd247ddfab0e09494ea29ca5bffa4209530bf Mon Sep 17 00:00:00 2001 From: AlwaysData Date: Wed, 9 Mar 2022 19:25:04 -0500 Subject: [PATCH 17/39] init commit of async contract (#2377) Create Base/Async structure for ContractFunctions, ContractEvents, Contract, ContractConstructor, ContractFunction, ContractEvent, ContractCaller classes --- tests/core/contracts/conftest.py | 50 + web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 1007 +++++++++++++++------ web3/providers/eth_tester/main.py | 48 +- web3/providers/eth_tester/middleware.py | 248 ++--- 5 files changed, 993 insertions(+), 362 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 2941572be8..e066bd4f3e 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -5,7 +5,12 @@ from eth_utils import ( event_signature_to_log_topic, ) +from eth_utils.toolz import ( + identity, +) +import pytest_asyncio +from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, CONTRACT_EMITTER_CODE, @@ -46,6 +51,15 @@ REVERT_CONTRACT_BYTECODE, REVERT_CONTRACT_RUNTIME_CODE, ) +from web3.contract import ( + AsyncContract, +) +from web3.eth import ( + AsyncEth, +) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) CONTRACT_NESTED_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; @@ -1036,3 +1050,39 @@ def estimateGas(request): @pytest.fixture def buildTransaction(request): return functools.partial(invoke_contract, api_call_desig='buildTransaction') + + +@pytest_asyncio.fixture() +async def async_deploy(web3, Contract, apply_func=identity, args=None): + args = args or [] + deploy_txn = await Contract.constructor(*args).transact() + deploy_receipt = await web3.eth.wait_for_transaction_receipt(deploy_txn) + assert deploy_receipt is not None + address = apply_func(deploy_receipt['contractAddress']) + contract = Contract(address=address) + assert contract.address == address + assert len(await web3.eth.get_code(contract.address)) > 0 + return contract + + +@pytest_asyncio.fixture() +async def async_w3(): + provider = AsyncEthereumTesterProvider() + w3 = Web3(provider, modules={'eth': [AsyncEth]}, + middlewares=provider.middlewares) + w3.eth.default_account = await w3.eth.coinbase + return w3 + + +@pytest_asyncio.fixture() +def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): + contract = AsyncContract.factory(async_w3, + abi=MATH_ABI, + bytecode=MATH_CODE, + bytecode_runtime=MATH_RUNTIME) + return contract + + +@pytest_asyncio.fixture() +async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): + return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index 73abb9cbfb..fbe562bff4 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): # type: ignore + with pytest.raises(expected): w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index 38e98b4152..9d661ca02d 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -6,6 +6,7 @@ from typing import ( TYPE_CHECKING, Any, + Awaitable, Callable, Collection, Dict, @@ -102,6 +103,7 @@ BASE_RETURN_NORMALIZERS, normalize_abi, normalize_address, + normalize_address_no_ens, normalize_bytecode, ) from web3._utils.transactions import ( @@ -139,6 +141,7 @@ BlockIdentifier, CallOverrideParams, EventData, + FilterParams, FunctionIdentifier, LogReceipt, TxParams, @@ -151,11 +154,17 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] -class ContractFunctions: +class BaseContractFunctions: """Class containing contract function objects """ - def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None) -> None: + def __init__(self, + abi: ABI, + w3: 'Web3', + contract_function_class: Union[Type['ContractFunction'], + Type['AsyncContractFunction']], + address: Optional[ChecksumAddress] = None) -> None: + self.abi = abi self.w3 = w3 self.address = address @@ -166,7 +175,7 @@ def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = No setattr( self, func['name'], - ContractFunction.factory( + contract_function_class.factory( func['name'], w3=self.w3, contract_abi=self.abi, @@ -208,7 +217,25 @@ def __hasattr__(self, event_name: str) -> bool: return False -class ContractEvents: +class ContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, w3, ContractFunction, address) + + +class AsyncContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, w3, AsyncContractFunction, address) + + +class BaseContractEvents: """Class containing contract event objects This is available via: @@ -229,7 +256,9 @@ class ContractEvents: """ - def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None) -> None: + def __init__(self, abi: ABI, w3: 'Web3', + contract_event_type: Union[Type['ContractEvent'], Type['AsyncContractEvent']], + address: Optional[ChecksumAddress] = None) -> None: if abi: self.abi = abi self._events = filter_by_type('event', self.abi) @@ -237,7 +266,7 @@ def __init__(self, abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = No setattr( self, event['name'], - ContractEvent.factory( + contract_event_type.factory( event['name'], w3=w3, contract_abi=self.abi, @@ -276,7 +305,21 @@ def __hasattr__(self, event_name: str) -> bool: return False -class Contract: +class ContractEvents(BaseContractEvents): + + def __init__(self, abi: ABI, w3: 'Web3', + address: Optional[ChecksumAddress] = None) -> None: + super().__init__(abi, w3, ContractEvent, address) + + +class AsyncContractEvents(BaseContractEvents): + + def __init__(self, abi: ABI, w3: 'Web3', + address: Optional[ChecksumAddress] = None) -> None: + super().__init__(abi, w3, AsyncContractEvent, address) + + +class BaseContract: """Base class for Contract proxy classes. First you need to create your Contract classes using @@ -309,12 +352,6 @@ class Contract: bytecode_runtime = None clone_bin = None - functions: ContractFunctions = None - caller: 'ContractCaller' = None - - #: Instance of :class:`ContractEvents` presenting available Event ABIs - events: ContractEvents = None - dev_doc = None interface = None metadata = None @@ -323,77 +360,6 @@ class Contract: src_map_runtime = None user_doc = None - def __init__(self, address: Optional[ChecksumAddress] = None) -> None: - """Create a new smart contract proxy object. - - :param address: Contract address as 0x hex string - """ - if self.w3 is None: - raise AttributeError( - 'The `Contract` class has not been initialized. Please use the ' - '`web3.contract` interface to create your contract class.' - ) - - if address: - self.address = normalize_address(self.w3.ens, address) - - if not self.address: - raise TypeError("The address argument is required to instantiate a contract.") - - self.functions = ContractFunctions(self.abi, self.w3, self.address) - self.caller = ContractCaller(self.abi, self.w3, self.address) - self.events = ContractEvents(self.abi, self.w3, self.address) - self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) - self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) - - @classmethod - def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': - - kwargs['w3'] = w3 - - normalizers = { - 'abi': normalize_abi, - 'address': partial(normalize_address, kwargs['w3'].ens), - 'bytecode': normalize_bytecode, - 'bytecode_runtime': normalize_bytecode, - } - - contract = cast(Contract, PropertyCheckingFactory( - class_name or cls.__name__, - (cls,), - kwargs, - normalizers=normalizers, - )) - contract.functions = ContractFunctions(contract.abi, contract.w3) - contract.caller = ContractCaller(contract.abi, contract.w3, contract.address) - contract.events = ContractEvents(contract.abi, contract.w3) - contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3) - contract.receive = Contract.get_receive_function(contract.abi, contract.w3) - - return contract - - # - # Contract Methods - # - @classmethod - def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': - """ - :param args: The contract constructor arguments as positional arguments - :param kwargs: The contract constructor arguments as keyword arguments - :return: a contract constructor object - """ - if cls.bytecode is None: - raise ValueError( - "Cannot call constructor on a contract that does not have 'bytecode' associated " - "with it" - ) - - return ContractConstructor(cls.w3, - cls.abi, - cls.bytecode, - *args, - **kwargs) - # Public API # @combomethod @@ -415,13 +381,14 @@ def encodeABI(cls, fn_name: str, args: Optional[Any] = None, return encode_abi(cls.w3, fn_abi, fn_arguments, data) @combomethod - def all_functions(self) -> List['ContractFunction']: - return find_functions_by_identifier( + def all_functions(self) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: + return self.find_functions_by_identifier( self.abi, self.w3, self.address, lambda _: True ) @combomethod - def get_function_by_signature(self, signature: str) -> 'ContractFunction': + def get_function_by_signature(self, signature: str + ) -> Union['ContractFunction', 'AsyncContractFunction']: if ' ' in signature: raise ValueError( 'Function signature should not contain any spaces. ' @@ -431,35 +398,40 @@ def get_function_by_signature(self, signature: str) -> 'ContractFunction': def callable_check(fn_abi: ABIFunction) -> bool: return abi_to_signature(fn_abi) == signature - fns = find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) + fns = self.find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) return get_function_by_identifier(fns, 'signature') @combomethod - def find_functions_by_name(self, fn_name: str) -> List['ContractFunction']: + def find_functions_by_name(self, fn_name: str + ) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: def callable_check(fn_abi: ABIFunction) -> bool: return fn_abi['name'] == fn_name - return find_functions_by_identifier( + return self.find_functions_by_identifier( self.abi, self.w3, self.address, callable_check ) @combomethod - def get_function_by_name(self, fn_name: str) -> 'ContractFunction': + def get_function_by_name(self, fn_name: str + ) -> Union['ContractFunction', 'AsyncContractFunction']: fns = self.find_functions_by_name(fn_name) return get_function_by_identifier(fns, 'name') @combomethod - def get_function_by_selector(self, selector: Union[bytes, int, HexStr]) -> 'ContractFunction': + def get_function_by_selector(self, selector: Union[bytes, int, HexStr] + ) -> Union['ContractFunction', 'AsyncContractFunction']: def callable_check(fn_abi: ABIFunction) -> bool: # typed dict cannot be used w/ a normal Dict # https://github.com/python/mypy/issues/4976 return encode_hex(function_abi_to_4byte_selector(fn_abi)) == to_4byte_hex(selector) # type: ignore # noqa: E501 - fns = find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) + fns = self.find_functions_by_identifier(self.abi, self.w3, self.address, callable_check) return get_function_by_identifier(fns, 'selector') @combomethod - def decode_function_input(self, data: HexStr) -> Tuple['ContractFunction', Dict[str, Any]]: + def decode_function_input(self, data: HexStr + ) -> Union[Tuple['ContractFunction', Dict[str, Any]], + Tuple['AsyncContractFunction', Dict[str, Any]]]: # type ignored b/c expects data arg to be HexBytes data = HexBytes(data) # type: ignore selector, params = data[:4], data[4:] @@ -474,16 +446,18 @@ def decode_function_input(self, data: HexStr) -> Tuple['ContractFunction', Dict[ return func, dict(zip(names, normalized)) @combomethod - def find_functions_by_args(self, *args: Any) -> List['ContractFunction']: + def find_functions_by_args(self, *args: Any + ) -> Union[List['ContractFunction'], List['AsyncContractFunction']]: def callable_check(fn_abi: ABIFunction) -> bool: return check_if_arguments_can_be_encoded(fn_abi, self.w3.codec, args=args, kwargs={}) - return find_functions_by_identifier( + return self.find_functions_by_identifier( self.abi, self.w3, self.address, callable_check ) @combomethod - def get_function_by_args(self, *args: Any) -> 'ContractFunction': + def get_function_by_args(self, *args: Any + ) -> Union['ContractFunction', 'AsyncContractFunction']: fns = self.find_functions_by_args(*args) return get_function_by_identifier(fns, 'args') @@ -529,6 +503,115 @@ def _find_matching_event_abi( event_name=event_name, argument_names=argument_names) + @combomethod + def _encode_constructor_data(cls, args: Optional[Any] = None, + kwargs: Optional[Any] = None) -> HexStr: + constructor_abi = get_constructor_abi(cls.abi) + + if constructor_abi: + if args is None: + args = tuple() + if kwargs is None: + kwargs = {} + + arguments = merge_args_and_kwargs(constructor_abi, args, kwargs) + + deploy_data = add_0x_prefix( + encode_abi(cls.w3, constructor_abi, arguments, data=cls.bytecode) + ) + else: + if args is not None or kwargs is not None: + msg = "Constructor args were provided, but no constructor function was provided." + raise TypeError(msg) + + deploy_data = to_hex(cls.bytecode) + + return deploy_data + + @combomethod + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List[Any]: + raise NotImplementedError("This method should be implemented in the inherited class") + + +class Contract(BaseContract): + + functions: ContractFunctions = None + caller: 'ContractCaller' = None + + #: Instance of :class:`ContractEvents` presenting available Event ABIs + events: ContractEvents = None + + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + """Create a new smart contract proxy object. + + :param address: Contract address as 0x hex string""" + if self.w3 is None: + raise AttributeError( + 'The `Contract` class has not been initialized. Please use the ' + '`web3.contract` interface to create your contract class.' + ) + + if address: + self.address = normalize_address(self.w3.ens, address) + + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + + self.functions = ContractFunctions(self.abi, self.w3, self.address) + self.caller = ContractCaller(self.abi, self.w3, self.address) + self.events = ContractEvents(self.abi, self.w3, self.address) + self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) + self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) + + @classmethod + def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + kwargs['w3'] = w3 + + normalizers = { + 'abi': normalize_abi, + 'address': partial(normalize_address, kwargs['w3'].ens), + 'bytecode': normalize_bytecode, + 'bytecode_runtime': normalize_bytecode, + } + + contract = cast(Contract, PropertyCheckingFactory( + class_name or cls.__name__, + (cls,), + kwargs, + normalizers=normalizers, + )) + contract.functions = ContractFunctions(contract.abi, contract.w3) + contract.caller = ContractCaller(contract.abi, contract.w3, contract.address) + contract.events = ContractEvents(contract.abi, contract.w3) + contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3) + contract.receive = Contract.get_receive_function(contract.abi, contract.w3) + + return contract + + @classmethod + def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': + """ + :param args: The contract constructor arguments as positional arguments + :param kwargs: The contract constructor arguments as keyword arguments + :return: a contract constructor object + """ + if cls.bytecode is None: + raise ValueError( + "Cannot call constructor on a contract that does not have 'bytecode' associated " + "with it" + ) + + return ContractConstructor(cls.w3, + cls.abi, + cls.bytecode, + *args, + **kwargs) + @staticmethod def get_fallback_function( abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None @@ -558,29 +641,125 @@ def get_receive_function( return cast('ContractFunction', NonExistentReceiveFunction()) @combomethod - def _encode_constructor_data(cls, args: Optional[Any] = None, - kwargs: Optional[Any] = None) -> HexStr: - constructor_abi = get_constructor_abi(cls.abi) + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List['ContractFunction']: + return find_functions_by_identifier(contract_abi, w3, address, callable_check) - if constructor_abi: - if args is None: - args = tuple() - if kwargs is None: - kwargs = {} - arguments = merge_args_and_kwargs(constructor_abi, args, kwargs) +class AsyncContract(BaseContract): - deploy_data = add_0x_prefix( - encode_abi(cls.w3, constructor_abi, arguments, data=cls.bytecode) + functions: AsyncContractFunctions = None + caller: 'AsyncContractCaller' = None + + #: Instance of :class:`ContractEvents` presenting available Event ABIs + events: AsyncContractEvents = None + + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + """Create a new smart contract proxy object. + + :param address: Contract address as 0x hex string""" + if self.w3 is None: + raise AttributeError( + 'The `Contract` class has not been initialized. Please use the ' + '`web3.contract` interface to create your contract class.' ) - else: - if args is not None or kwargs is not None: - msg = "Constructor args were provided, but no constructor function was provided." - raise TypeError(msg) - deploy_data = to_hex(cls.bytecode) + if address: + self.address = normalize_address_no_ens(address) - return deploy_data + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + self.functions = AsyncContractFunctions(self.abi, self.w3, self.address) + self.caller = AsyncContractCaller(self.abi, self.w3, self.address) + self.events = AsyncContractEvents(self.abi, self.w3, self.address) + self.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, self.address) + self.receive = AsyncContract.get_receive_function(self.abi, self.w3, self.address) + + @classmethod + def factory(cls, w3: 'Web3', + class_name: Optional[str] = None, + **kwargs: Any) -> 'AsyncContract': + kwargs['w3'] = w3 + + normalizers = { + 'abi': normalize_abi, + 'address': normalize_address_no_ens, + 'bytecode': normalize_bytecode, + 'bytecode_runtime': normalize_bytecode, + } + + contract = cast(AsyncContract, PropertyCheckingFactory( + class_name or cls.__name__, + (cls,), + kwargs, + normalizers=normalizers, + )) + contract.functions = AsyncContractFunctions(contract.abi, contract.w3) + contract.caller = AsyncContractCaller(contract.abi, contract.w3, contract.address) + contract.events = AsyncContractEvents(contract.abi, contract.w3) + contract.fallback = AsyncContract.get_fallback_function(contract.abi, contract.w3) + contract.receive = AsyncContract.get_receive_function(contract.abi, contract.w3) + return contract + + @classmethod + def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': + """ + :param args: The contract constructor arguments as positional arguments + :param kwargs: The contract constructor arguments as keyword arguments + :return: a contract constructor object + """ + if cls.bytecode is None: + raise ValueError( + "Cannot call constructor on a contract that does not have 'bytecode' associated " + "with it" + ) + + return AsyncContractConstructor(cls.w3, + cls.abi, + cls.bytecode, + *args, + **kwargs) + + @staticmethod + def get_fallback_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'AsyncContractFunction': + if abi and fallback_func_abi_exists(abi): + return AsyncContractFunction.factory( + 'fallback', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=FallbackFn)() + + return cast('AsyncContractFunction', NonExistentFallbackFunction()) + + @staticmethod + def get_receive_function( + abi: ABI, w3: 'Web3', address: Optional[ChecksumAddress] = None + ) -> 'AsyncContractFunction': + if abi and receive_func_abi_exists(abi): + return AsyncContractFunction.factory( + 'receive', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=ReceiveFn)() + + return cast('AsyncContractFunction', NonExistentReceiveFunction()) + + @combomethod + def find_functions_by_identifier(cls, + contract_abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + callable_check: Callable[..., Any] + ) -> List['AsyncContractFunction']: + return async_find_functions_by_identifier(contract_abi, w3, address, callable_check) def mk_collision_prop(fn_name: str) -> Callable[[], None]: @@ -591,7 +770,7 @@ def collision_fn() -> NoReturn: return collision_fn -class ContractConstructor: +class BaseContractConstructor: """ Class for contract constructor API. """ @@ -644,8 +823,7 @@ def estimateGas( estimate_gas_transaction, block_identifier=block_identifier ) - @combomethod - def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: transact_transaction: TxParams = {} else: @@ -659,8 +837,7 @@ def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: transact_transaction['data'] = self.data_in_transaction - # TODO: handle asynchronous contract creation - return self.w3.eth.send_transaction(transact_transaction) + return transact_transaction @combomethod def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: @@ -694,6 +871,21 @@ def check_forbidden_keys_in_transaction( ) +class ContractConstructor(BaseContractConstructor): + + @combomethod + def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return self.w3.eth.send_transaction(self._get_transaction(transaction)) + + +class AsyncContractConstructor(BaseContractConstructor): + + @combomethod + async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return await self.w3.eth.send_transaction( # type: ignore + self._get_transaction(transaction)) + + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} @@ -844,7 +1036,7 @@ def __getattr__(self, attr: Any) -> Callable[[], None]: return self._raise_exception -class ContractFunction: +class BaseContractFunction: """Base class for contract functions A function accessed via the api contract.functions.myMethod(*args, **kwargs) @@ -864,20 +1056,6 @@ def __init__(self, abi: Optional[ABIFunction] = None) -> None: self.abi = abi self.fn_name = type(self).__name__ - def __call__(self, *args: Any, **kwargs: Any) -> 'ContractFunction': - clone = copy.copy(self) - if args is None: - clone.args = tuple() - else: - clone.args = args - - if kwargs is None: - clone.kwargs = {} - else: - clone.kwargs = kwargs - clone._set_function_info() - return clone - def _set_function_info(self) -> None: if not self.abi: self.abi = find_matching_fn_abi( @@ -897,35 +1075,8 @@ def _set_function_info(self) -> None: self.arguments = merge_args_and_kwargs(self.abi, self.args, self.kwargs) - def call( - self, transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest', - state_override: Optional[CallOverrideParams] = None, - ) -> Any: - """ - Execute a contract function call using the `eth_call` interface. - - This method prepares a ``Caller`` object that exposes the contract - functions and public variables as callable Python functions. - - Reading a public ``owner`` address variable example: - - .. code-block:: python - - ContractFactory = w3.eth.contract( - abi=wallet_contract_definition["abi"] - ) - - # Not a real contract address - contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") + def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams: - # Read "owner" public variable - addr = contract.functions.owner().call() - - :param transaction: Dictionary of transaction info for web3 interface - :return: ``Caller`` object that has contract public functions - and variables exposed as Python methods - """ if transaction is None: call_transaction: TxParams = {} else: @@ -952,21 +1103,7 @@ def call( "Please ensure that this contract instance has an address." ) - block_id = parse_block_identifier(self.w3, block_identifier) - - return call_contract_function( - self.w3, - self.address, - self._return_data_normalizers, - self.function_identifier, - call_transaction, - block_id, - self.contract_abi, - self.abi, - state_override, - *self.args, - **self.kwargs - ) + return call_transaction def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: if transaction is None: @@ -1093,10 +1230,6 @@ def _encode_transaction_data(cls) -> HexStr: _return_data_normalizers: Optional[Tuple[Callable[..., Any], ...]] = tuple() - @classmethod - def factory(cls, class_name: str, **kwargs: Any) -> 'ContractFunction': - return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) - def __repr__(self) -> str: if self.abi: _repr = ' str: return '' % self.fn_name -class ContractEvent: +class ContractFunction(BaseContractFunction): + + def __call__(self, + *args: Any, + **kwargs: Any) -> 'ContractFunction': + clone = copy.copy(self) + if args is None: + clone.args = tuple() + else: + clone.args = args + + if kwargs is None: + clone.kwargs = {} + else: + clone.kwargs = kwargs + clone._set_function_info() + return clone + + def call(self, transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest', + state_override: Optional[CallOverrideParams] = None, + ) -> Any: + """ + Execute a contract function call using the `eth_call` interface. + + This method prepares a ``Caller`` object that exposes the contract + functions and public variables as callable Python functions. + + Reading a public ``owner`` address variable example: + + .. code-block:: python + + ContractFactory = w3.eth.contract( + abi=wallet_contract_definition["abi"] + ) + + # Not a real contract address + contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") + + # Read "owner" public variable + addr = contract.functions.owner().call() + + :param transaction: Dictionary of transaction info for web3 interface + :return: ``Caller`` object that has contract public functions + and variables exposed as Python methods + """ + call_transaction = self._get_call_txparams(transaction) + + block_id = parse_block_identifier(self.w3, block_identifier) + + return call_contract_function(self.w3, + self.address, + self._return_data_normalizers, + self.function_identifier, + call_transaction, + block_id, + self.contract_abi, + self.abi, + state_override, + *self.args, + **self.kwargs + ) + + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> 'ContractFunction': + return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + + +class AsyncContractFunction(BaseContractFunction): + + def __call__(self, + *args: Any, + **kwargs: Any) -> 'AsyncContractFunction': + clone = copy.copy(self) + if args is None: + clone.args = tuple() + else: + clone.args = args + + if kwargs is None: + clone.kwargs = {} + else: + clone.kwargs = kwargs + clone._set_function_info() + return clone + + async def call( + self, transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest', + state_override: Optional[CallOverrideParams] = None, + ) -> Any: + """ + Execute a contract function call using the `eth_call` interface. + + This method prepares a ``Caller`` object that exposes the contract + functions and public variables as callable Python functions. + + Reading a public ``owner`` address variable example: + + .. code-block:: python + + ContractFactory = w3.eth.contract( + abi=wallet_contract_definition["abi"] + ) + + # Not a real contract address + contract = ContractFactory("0x2f70d3d26829e412A602E83FE8EeBF80255AEeA5") + + # Read "owner" public variable + addr = contract.functions.owner().call() + + :param transaction: Dictionary of transaction info for web3 interface + :return: ``Caller`` object that has contract public functions + and variables exposed as Python methods + """ + call_transaction = self._get_call_txparams(transaction) + + block_id = await async_parse_block_identifier(self.w3, block_identifier) + + return await async_call_contract_function( + self.w3, + self.address, + self._return_data_normalizers, + self.function_identifier, + call_transaction, + block_id, + self.contract_abi, + self.abi, + state_override, + *self.args, + **self.kwargs + ) + + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> 'AsyncContractFunction': + return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + + +class BaseContractEvent: """Base class for contract events An event accessed via the api contract.events.myEvents(*args, **kwargs) @@ -1243,6 +1514,54 @@ def build_filter(self) -> EventFilterBuilder: builder.address = self.address return builder + @combomethod + def _get_event_filter_params(self, + abi: ABIEvent, + argument_filters: Optional[Dict[str, Any]] = None, + fromBlock: Optional[BlockIdentifier] = None, + toBlock: Optional[BlockIdentifier] = None, + blockHash: Optional[HexBytes] = None) -> FilterParams: + + if not self.address: + raise TypeError("This method can be only called on " + "an instated contract with an address") + + 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 + data_filter_set, event_filter_params = construct_event_filter_params( + abi, + self.w3.codec, + contract_address=self.address, + argument_filters=_filters, + fromBlock=fromBlock, + toBlock=toBlock, + address=self.address, + ) + + if blockHash is not None: + event_filter_params['blockHash'] = blockHash + + return event_filter_params + + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: + return PropertyCheckingFactory(class_name, (cls,), kwargs) + + +class ContractEvent(BaseContractEvent): + @combomethod def getLogs(self, argument_filters: Optional[Dict[str, Any]] = None, @@ -1303,52 +1622,93 @@ def getLogs(self, same time as fromBlock or toBlock :yield: Tuple of :class:`AttributeDict` instances """ + abi = self._get_event_abi() + # Call JSON-RPC API + logs = self.w3.eth.get_logs(self._get_event_filter_params(abi, + argument_filters, + fromBlock, + toBlock, + blockHash)) - if not self.address: - raise TypeError("This method can be only called on " - "an instated contract with an address") + # Convert raw binary data to Python proxy objects as described by ABI + return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) - abi = self._get_event_abi() - if argument_filters is None: - argument_filters = dict() +class AsyncContractEvent(BaseContractEvent): - _filters = dict(**argument_filters) + @combomethod + async def getLogs(self, + argument_filters: Optional[Dict[str, Any]] = None, + fromBlock: Optional[BlockIdentifier] = None, + toBlock: Optional[BlockIdentifier] = None, + blockHash: Optional[HexBytes] = None) -> Awaitable[Iterable[EventData]]: + """Get events for this contract instance using eth_getLogs API. - 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') + 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. - # Construct JSON-RPC raw filter presentation based on human readable Python descriptions - # Namely, convert event names to their keccak signatures - data_filter_set, event_filter_params = construct_event_filter_params( - abi, - self.w3.codec, - contract_address=self.address, - argument_filters=_filters, - fromBlock=fromBlock, - toBlock=toBlock, - address=self.address, - ) + 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. - if blockHash is not None: - event_filter_params['blockHash'] = blockHash + Example - how to get all ERC-20 token transactions + for the latest 10 blocks: + + .. code-block:: python + + from = max(mycontract.web3.eth.block_number - 10, 1) + to = mycontract.web3.eth.block_number + + 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 + """ + abi = self._get_event_abi() # Call JSON-RPC API - logs = self.w3.eth.get_logs(event_filter_params) + logs = await self.w3.eth.get_logs(self._get_event_filter_params(abi, # type: ignore + argument_filters, + fromBlock, + toBlock, + blockHash)) # Convert raw binary data to Python proxy objects as described by ABI - return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) - - @classmethod - def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: - return PropertyCheckingFactory(class_name, (cls,), kwargs) + return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs) # type: ignore -class ContractCaller: +class BaseContractCaller: """ An alternative Contract API. @@ -1375,7 +1735,11 @@ def __init__(self, w3: 'Web3', address: ChecksumAddress, transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest') -> None: + block_identifier: BlockIdentifier = 'latest', + contract_function_class: + Optional[Union[Type[ContractFunction], + Type[AsyncContractFunction]]] = ContractFunction + ) -> None: self.w3 = w3 self.address = address self.abi = abi @@ -1387,7 +1751,7 @@ def __init__(self, self._functions = filter_by_type('function', self.abi) for func in self._functions: - fn: ContractFunction = ContractFunction.factory( + fn: BaseContractFunction = contract_function_class.factory( func['name'], w3=self.w3, contract_abi=self.abi, @@ -1429,6 +1793,29 @@ def __hasattr__(self, event_name: str) -> bool: except ABIFunctionNotFound: return False + @staticmethod + def call_function( + fn: ContractFunction, + *args: Any, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest', + **kwargs: Any + ) -> Any: + if transaction is None: + transaction = {} + return fn(*args, **kwargs).call(transaction, block_identifier) + + +class ContractCaller(BaseContractCaller): + def __init__(self, + abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, w3, address, + transaction, block_identifier, ContractFunction) + def __call__( self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest' ) -> 'ContractCaller': @@ -1440,17 +1827,28 @@ def __call__( transaction=transaction, block_identifier=block_identifier) - @staticmethod - def call_function( - fn: ContractFunction, - *args: Any, - transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest', - **kwargs: Any - ) -> Any: + +class AsyncContractCaller(BaseContractCaller): + + def __init__(self, + abi: ABI, + w3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, w3, address, + transaction, block_identifier, AsyncContractFunction) + + def __call__( + self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest' + ) -> 'AsyncContractCaller': if transaction is None: transaction = {} - return fn(*args, **kwargs).call(transaction, block_identifier) + return type(self)(self.abi, + self.w3, + self.address, + transaction=transaction, + block_identifier=block_identifier) def check_for_forbidden_api_filter_arguments( @@ -1471,39 +1869,19 @@ def check_for_forbidden_api_filter_arguments( "method.") -def call_contract_function( - w3: 'Web3', - address: ChecksumAddress, - normalizers: Tuple[Callable[..., Any], ...], - function_identifier: FunctionIdentifier, - transaction: TxParams, - block_id: Optional[BlockIdentifier] = None, - contract_abi: Optional[ABI] = None, - fn_abi: Optional[ABIFunction] = None, - state_override: Optional[CallOverrideParams] = None, - *args: Any, - **kwargs: Any) -> Any: +def _call_contract_function(w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + return_data: Union[bytes, bytearray], + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> Any: """ Helper function for interacting with a contract function using the `eth_call` API. """ - call_transaction = prepare_transaction( - address, - w3, - fn_identifier=function_identifier, - contract_abi=contract_abi, - fn_abi=fn_abi, - transaction=transaction, - fn_args=args, - fn_kwargs=kwargs, - ) - - return_data = w3.eth.call( - call_transaction, - block_identifier=block_id, - state_override=state_override, - ) - if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) @@ -1541,6 +1919,94 @@ def call_contract_function( return normalized_data +def call_contract_function( + w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + transaction: TxParams, + block_id: Optional[BlockIdentifier] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + state_override: Optional[CallOverrideParams] = None, + *args: Any, + **kwargs: Any) -> Any: + """ + Helper function for interacting with a contract function using the + `eth_call` API. + """ + call_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = w3.eth.call( + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(w3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + +async def async_call_contract_function( + w3: 'Web3', + address: ChecksumAddress, + normalizers: Tuple[Callable[..., Any], ...], + function_identifier: FunctionIdentifier, + transaction: TxParams, + block_id: Optional[BlockIdentifier] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + state_override: Optional[CallOverrideParams] = None, + *args: Any, + **kwargs: Any) -> Any: + """ + Helper function for interacting with a contract function using the + `eth_call` API. + """ + call_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = await w3.eth.call( # type: ignore + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(w3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier: if isinstance(block_identifier, int): return parse_block_identifier_int(w3, block_identifier) @@ -1552,6 +2018,19 @@ def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> Blo raise BlockNumberOutofRange +async def async_parse_block_identifier(w3: 'Web3', + block_identifier: BlockIdentifier + ) -> BlockIdentifier: + if isinstance(block_identifier, int): + return parse_block_identifier_int(w3, block_identifier) + elif block_identifier in ['latest', 'earliest', 'pending']: + return block_identifier + elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier): + return await w3.eth.get_block(block_identifier)['number'] # type: ignore + else: + raise BlockNumberOutofRange + + def parse_block_identifier_int(w3: 'Web3', block_identifier_int: int) -> BlockNumber: if block_identifier_int >= 0: block_num = block_identifier_int @@ -1668,6 +2147,24 @@ def find_functions_by_identifier( ] +def async_find_functions_by_identifier( + contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] +) -> List[AsyncContractFunction]: + fns_abi = filter_by_type('function', contract_abi) + return [ + AsyncContractFunction.factory( + fn_abi['name'], + w3=w3, + contract_abi=contract_abi, + address=address, + function_identifier=fn_abi['name'], + abi=fn_abi + ) + for fn_abi in fns_abi + if callable_check(fn_abi) + ] + + def get_function_by_identifier( fns: Sequence[ContractFunction], identifier: str ) -> ContractFunction: diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index e45df5082b..ffb20bd2e1 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -17,6 +17,9 @@ from web3._utils.compat import ( Literal, ) +from web3.middleware.buffered_gas_estimate import ( + async_buffered_gas_estimate_middleware, +) from web3.providers import ( BaseProvider, ) @@ -29,6 +32,8 @@ ) from .middleware import ( + async_default_transaction_fields_middleware, + async_ethereum_tester_middleware, default_transaction_fields_middleware, ethereum_tester_middleware, ) @@ -43,13 +48,46 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): + middlewares = ( + async_buffered_gas_estimate_middleware, + async_default_transaction_fields_middleware, + async_ethereum_tester_middleware + ) + def __init__(self) -> None: - self.eth_tester = EthereumTesterProvider() + from eth_tester import EthereumTester + from web3.providers.eth_tester.defaults import API_ENDPOINTS + self.ethereum_tester = EthereumTester() + self.api_endpoints = API_ENDPOINTS + + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + namespace, _, endpoint = method.partition('_') + from eth_tester.exceptions import TransactionFailed + try: + delegator = self.api_endpoints[namespace][endpoint] + except KeyError: + return RPCResponse( + {"error": f"Unknown RPC Endpoint: {method}"} + ) + try: + response = delegator(self.ethereum_tester, params) + except NotImplementedError: + return RPCResponse( + {"error": f"RPC Endpoint has not been implemented: {method}"} + ) + except TransactionFailed as e: + try: + reason = decode_single('(string)', e.args[0].args[0][4:])[0] + except (InsufficientDataBytes, AttributeError): + reason = e.args[0] + raise TransactionFailed(f'execution reverted: {reason}') + else: + return { + 'result': response, + } - async def make_request( - self, method: RPCEndpoint, params: Any - ) -> RPCResponse: - return self.eth_tester.make_request(method, params) + async def isConnected(self) -> Literal[True]: + return True class EthereumTesterProvider(BaseProvider): diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 18fbd42965..e230129af4 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -3,6 +3,8 @@ TYPE_CHECKING, Any, Callable, + Dict, + Optional, ) from eth_typing import ( @@ -39,7 +41,11 @@ from web3.middleware import ( construct_formatting_middleware, ) +from web3.middleware.formatting import ( + async_construct_formatting_middleware, +) from web3.types import ( + Middleware, RPCEndpoint, RPCResponse, TxParams, @@ -181,109 +187,121 @@ def is_hexstr(value: Any) -> bool: } receipt_result_formatter = apply_formatters_to_dict(RECEIPT_RESULT_FORMATTERS) +request_formatters = { + # Eth + RPCEndpoint('eth_getBlockByNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getFilterChanges'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getFilterLogs'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getUncleCountByBlockNumber'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getTransactionByBlockHashAndIndex'): apply_formatters_to_args( + identity, + to_integer_if_hex, + ), + RPCEndpoint('eth_getTransactionByBlockNumberAndIndex'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + to_integer_if_hex, + ), + RPCEndpoint('eth_getUncleByBlockNumberAndIndex'): apply_formatters_to_args( + apply_formatter_if(is_not_named_block, to_integer_if_hex), + to_integer_if_hex, + ), + RPCEndpoint('eth_newFilter'): apply_formatters_to_args( + filter_request_transformer, + ), + RPCEndpoint('eth_getLogs'): apply_formatters_to_args( + filter_request_transformer, + ), + RPCEndpoint('eth_sendTransaction'): apply_formatters_to_args( + transaction_request_transformer, + ), + RPCEndpoint('eth_estimateGas'): apply_formatters_to_args( + transaction_request_transformer, + ), + RPCEndpoint('eth_call'): apply_formatters_to_args( + transaction_request_transformer, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_uninstallFilter'): apply_formatters_to_args(hex_to_integer), + RPCEndpoint('eth_getCode'): apply_formatters_to_args( + identity, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + RPCEndpoint('eth_getBalance'): apply_formatters_to_args( + identity, + apply_formatter_if(is_not_named_block, to_integer_if_hex), + ), + # EVM + RPCEndpoint('evm_revert'): apply_formatters_to_args(hex_to_integer), + # Personal + RPCEndpoint('personal_sendTransaction'): apply_formatters_to_args( + transaction_request_transformer, + identity, + ), +} +result_formatters: Optional[Dict[RPCEndpoint, Callable[..., Any]]] = { + RPCEndpoint('eth_getBlockByHash'): apply_formatter_if( + is_dict, + block_result_remapper, + ), + RPCEndpoint('eth_getBlockByNumber'): apply_formatter_if( + is_dict, + block_result_remapper, + ), + RPCEndpoint('eth_getBlockTransactionCountByHash'): apply_formatter_if( + is_dict, + transaction_result_remapper, + ), + RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatter_if( + is_dict, + transaction_result_remapper, + ), + RPCEndpoint('eth_getTransactionByHash'): apply_formatter_if( + is_dict, + compose(transaction_result_remapper, transaction_result_formatter), + ), + RPCEndpoint('eth_getTransactionReceipt'): apply_formatter_if( + is_dict, + compose(receipt_result_remapper, receipt_result_formatter), + ), + RPCEndpoint('eth_newFilter'): integer_to_hex, + RPCEndpoint('eth_newBlockFilter'): integer_to_hex, + RPCEndpoint('eth_newPendingTransactionFilter'): integer_to_hex, + RPCEndpoint('eth_getLogs'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + RPCEndpoint('eth_getFilterChanges'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + RPCEndpoint('eth_getFilterLogs'): apply_formatter_if( + is_array_of_dicts, + apply_formatter_to_array(log_result_remapper), + ), + # EVM + RPCEndpoint('evm_snapshot'): integer_to_hex, +} + + +async def async_ethereum_tester_middleware(make_request, web3: "Web3" # type: ignore + ) -> Middleware: + middleware = await async_construct_formatting_middleware( + request_formatters=request_formatters, + result_formatters=result_formatters + ) + return await middleware(make_request, web3) + ethereum_tester_middleware = construct_formatting_middleware( - request_formatters={ - # Eth - RPCEndpoint('eth_getBlockByNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getFilterChanges'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getFilterLogs'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getUncleCountByBlockNumber'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getTransactionByBlockHashAndIndex'): apply_formatters_to_args( - identity, - to_integer_if_hex, - ), - RPCEndpoint('eth_getTransactionByBlockNumberAndIndex'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - to_integer_if_hex, - ), - RPCEndpoint('eth_getUncleByBlockNumberAndIndex'): apply_formatters_to_args( - apply_formatter_if(is_not_named_block, to_integer_if_hex), - to_integer_if_hex, - ), - RPCEndpoint('eth_newFilter'): apply_formatters_to_args( - filter_request_transformer, - ), - RPCEndpoint('eth_getLogs'): apply_formatters_to_args( - filter_request_transformer, - ), - RPCEndpoint('eth_sendTransaction'): apply_formatters_to_args( - transaction_request_transformer, - ), - RPCEndpoint('eth_estimateGas'): apply_formatters_to_args( - transaction_request_transformer, - ), - RPCEndpoint('eth_call'): apply_formatters_to_args( - transaction_request_transformer, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_uninstallFilter'): apply_formatters_to_args(hex_to_integer), - RPCEndpoint('eth_getCode'): apply_formatters_to_args( - identity, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - RPCEndpoint('eth_getBalance'): apply_formatters_to_args( - identity, - apply_formatter_if(is_not_named_block, to_integer_if_hex), - ), - # EVM - RPCEndpoint('evm_revert'): apply_formatters_to_args(hex_to_integer), - # Personal - RPCEndpoint('personal_sendTransaction'): apply_formatters_to_args( - transaction_request_transformer, - identity, - ), - }, - result_formatters={ - RPCEndpoint('eth_getBlockByHash'): apply_formatter_if( - is_dict, - block_result_remapper, - ), - RPCEndpoint('eth_getBlockByNumber'): apply_formatter_if( - is_dict, - block_result_remapper, - ), - RPCEndpoint('eth_getBlockTransactionCountByHash'): apply_formatter_if( - is_dict, - transaction_result_remapper, - ), - RPCEndpoint('eth_getBlockTransactionCountByNumber'): apply_formatter_if( - is_dict, - transaction_result_remapper, - ), - RPCEndpoint('eth_getTransactionByHash'): apply_formatter_if( - is_dict, - compose(transaction_result_remapper, transaction_result_formatter), - ), - RPCEndpoint('eth_getTransactionReceipt'): apply_formatter_if( - is_dict, - compose(receipt_result_remapper, receipt_result_formatter), - ), - RPCEndpoint('eth_newFilter'): integer_to_hex, - RPCEndpoint('eth_newBlockFilter'): integer_to_hex, - RPCEndpoint('eth_newPendingTransactionFilter'): integer_to_hex, - RPCEndpoint('eth_getLogs'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - RPCEndpoint('eth_getFilterChanges'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - RPCEndpoint('eth_getFilterLogs'): apply_formatter_if( - is_array_of_dicts, - apply_formatter_to_array(log_result_remapper), - ), - # EVM - RPCEndpoint('evm_snapshot'): integer_to_hex, - }, + request_formatters=request_formatters, + result_formatters=result_formatters ) @@ -313,6 +331,17 @@ def fill_default( return assoc(transaction, field, guess_val) +async def async_fill_default( + field: str, guess_func: Callable[..., Any], web3: "Web3", transaction: TxParams +) -> TxParams: + # type ignored b/c TxParams keys must be string literal types + if field in transaction and transaction[field] is not None: # type: ignore + return transaction + else: + guess_val = guess_func(web3, transaction) + return assoc(transaction, field, guess_val) + + def default_transaction_fields_middleware( make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: @@ -332,3 +361,20 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: else: return make_request(method, params) return middleware + + +async def async_default_transaction_fields_middleware( + make_request: Callable[[RPCEndpoint, Any], Any], web3: "Web3" +) -> Callable[[RPCEndpoint, Any], RPCResponse]: + async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: + if method in ( + 'eth_call', + 'eth_estimateGas', + 'eth_sendTransaction', + ): + filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) + return await make_request(method, + [filled_transaction] + list(params)[1:]) + else: + return await make_request(method, params) + return middleware # type: ignore From a2170e6a59821a2401184c2bfb34b5f4602e880c Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 10 Mar 2022 05:49:40 -0500 Subject: [PATCH 18/39] First test added --- tests/core/contracts/conftest.py | 7 +++--- .../contracts/test_contract_call_interface.py | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index e066bd4f3e..196a2def19 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1052,16 +1052,15 @@ def buildTransaction(request): return functools.partial(invoke_contract, api_call_desig='buildTransaction') -@pytest_asyncio.fixture() -async def async_deploy(web3, Contract, apply_func=identity, args=None): +async def async_deploy(async_web3, Contract, apply_func=identity, args=None): args = args or [] deploy_txn = await Contract.constructor(*args).transact() - deploy_receipt = await web3.eth.wait_for_transaction_receipt(deploy_txn) + deploy_receipt = await async_web3.eth.wait_for_transaction_receipt(deploy_txn) assert deploy_receipt is not None address = apply_func(deploy_receipt['contractAddress']) contract = Contract(address=address) assert contract.address == address - assert len(await web3.eth.get_code(contract.address)) > 0 + assert len(await async_web3.eth.get_code(contract.address)) > 0 return contract diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 6b1c1809b8..3433600360 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -879,3 +879,26 @@ def test_call_revert_contract(revert_contract): # which does not contain the revert reason. Avoid that by giving a gas # value. revert_contract.functions.revertWithMessage().call({'gas': 100000}) + +@pytest.mark.asyncio +async def test_async_call_with_no_arguments(async_math_contract, call): + result = await async_math_contract.functions.return13().call() + assert result == 13 + +@pytest.mark.asyncio +async def test_async_call_with_one_argument(async_math_contract, call): + result = await async_math_contract.functions.multiply7(3).call() + assert result == 21 + +@pytest.mark.asyncio +async def test_async_returns_data_from_specified_block(aync_w3, math_contract): + start_num = aync_w3.eth.get_block('latest').number + aync_w3.provider.make_request(method='evm_mine', params=[5]) + math_contract.functions.increment().transact() + math_contract.functions.increment().transact() + + output1 = math_contract.functions.counter().call(block_identifier=start_num + 6) + output2 = math_contract.functions.counter().call(block_identifier=start_num + 7) + + assert output1 == 1 + assert output2 == 2 \ No newline at end of file From 1c765e87f6527e9793f2a256eaa9de285f30bee5 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 10 Mar 2022 06:01:45 -0500 Subject: [PATCH 19/39] ContractConstructor --- web3/contract.py | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 9d661ca02d..386dbd938e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -840,25 +840,11 @@ def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: return transact_transaction @combomethod - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - """ - Build the transaction dictionary without sending - """ - - if transaction is None: - built_transaction: TxParams = {} - else: - built_transaction = cast(TxParams, dict(**transaction)) - self.check_forbidden_keys_in_transaction(built_transaction, - ["data", "to"]) - - if self.w3.eth.default_account is not empty: - # type ignored b/c check prevents an empty default_account - built_transaction.setdefault('from', self.w3.eth.default_account) # type: ignore - - built_transaction['data'] = self.data_in_transaction + def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + built_transaction = self._get_transaction(transaction) built_transaction['to'] = Address(b'') - return fill_transaction_defaults(self.w3, built_transaction) + return built_transaction + @staticmethod def check_forbidden_keys_in_transaction( @@ -877,6 +863,22 @@ class ContractConstructor(BaseContractConstructor): def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return self.w3.eth.send_transaction(self._get_transaction(transaction)) + @combomethod + def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + built_transaction = self._build_transaction(transaction) + return fill_transaction_defaults(self.w3, built_transaction) + + @combomethod + @deprecated_for("build_transaction") + def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + return self.build_transaction(transaction) + class AsyncContractConstructor(BaseContractConstructor): @@ -884,7 +886,14 @@ class AsyncContractConstructor(BaseContractConstructor): async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return await self.w3.eth.send_transaction( # type: ignore self._get_transaction(transaction)) - + + @combomethod + async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + built_transaction = self._build_transaction(transaction) + return fill_transaction_defaults(self.w3, built_transaction) class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} From 6e2ee8a06a13e0a4dde4d367fba27760a77f9229 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 10 Mar 2022 19:30:50 -0500 Subject: [PATCH 20/39] linting fixes --- tests/core/contracts/test_contract_call_interface.py | 5 ++++- web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 3433600360..a5411249de 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -880,16 +880,19 @@ def test_call_revert_contract(revert_contract): # value. revert_contract.functions.revertWithMessage().call({'gas': 100000}) + @pytest.mark.asyncio async def test_async_call_with_no_arguments(async_math_contract, call): result = await async_math_contract.functions.return13().call() assert result == 13 + @pytest.mark.asyncio async def test_async_call_with_one_argument(async_math_contract, call): result = await async_math_contract.functions.multiply7(3).call() assert result == 21 + @pytest.mark.asyncio async def test_async_returns_data_from_specified_block(aync_w3, math_contract): start_num = aync_w3.eth.get_block('latest').number @@ -901,4 +904,4 @@ async def test_async_returns_data_from_specified_block(aync_w3, math_contract): output2 = math_contract.functions.counter().call(block_identifier=start_num + 7) assert output1 == 1 - assert output2 == 2 \ No newline at end of file + assert output2 == 2 diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index fbe562bff4..73abb9cbfb 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): + with pytest.raises(expected): # type: ignore w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index 386dbd938e..264f54ce97 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -845,7 +845,6 @@ def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams built_transaction['to'] = Address(b'') return built_transaction - @staticmethod def check_forbidden_keys_in_transaction( transaction: TxParams, forbidden_keys: Optional[Collection[str]] = None @@ -886,7 +885,7 @@ class AsyncContractConstructor(BaseContractConstructor): async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return await self.w3.eth.send_transaction( # type: ignore self._get_transaction(transaction)) - + @combomethod async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: """ @@ -895,6 +894,7 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP built_transaction = self._build_transaction(transaction) return fill_transaction_defaults(self.w3, built_transaction) + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} From ad97b6e309ed20294b5b1581bad258c8cf917270 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 11 Mar 2022 05:31:26 -0500 Subject: [PATCH 21/39] eth.contract and linting --- ens/main.py | 6 ++- ethpm/package.py | 3 +- .../contracts/test_contract_call_interface.py | 18 ++++--- web3/eth.py | 52 ++++++++++--------- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/ens/main.py b/ens/main.py index 928a3106a4..1a47b33844 100644 --- a/ens/main.py +++ b/ens/main.py @@ -262,7 +262,8 @@ def resolver(self, normal_name: str) -> Optional['Contract']: resolver_addr = self.ens.caller.resolver(normal_name_to_hash(normal_name)) if is_none_or_zero_address(resolver_addr): return None - return self._resolverContract(address=resolver_addr) + # TODO: look at possibly removing type ignore when AsyncENS is written + return self._resolverContract(address=resolver_addr) # type: ignore def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -393,7 +394,8 @@ def _set_resolver( namehash, resolver_addr ).transact(transact) - return self._resolverContract(address=resolver_addr) + # TODO: look at possibly removing type ignore when AsyncENS is written + return self._resolverContract(address=resolver_addr) # type: ignore def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 139c412614..5eeba64aeb 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -309,7 +309,8 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac contract_instance = self.w3.eth.contract( address=address, **contract_kwargs ) - return contract_instance + # TODO: type ignore may be able to be removed after more of AsynContract is finished + return contract_instance # type: ignore # # Build Dependencies diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index a5411249de..44b7d9e5af 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -894,14 +894,16 @@ async def test_async_call_with_one_argument(async_math_contract, call): @pytest.mark.asyncio -async def test_async_returns_data_from_specified_block(aync_w3, math_contract): - start_num = aync_w3.eth.get_block('latest').number - aync_w3.provider.make_request(method='evm_mine', params=[5]) - math_contract.functions.increment().transact() - math_contract.functions.increment().transact() - - output1 = math_contract.functions.counter().call(block_identifier=start_num + 6) - output2 = math_contract.functions.counter().call(block_identifier=start_num + 7) +async def test_async_returns_data_from_specified_block(async_w3, async_math_contract): + start_num = await async_w3.eth.get_block('latest') + await async_w3.provider.make_request(method='evm_mine', params=[5]) + await async_math_contract.functions.increment().transact() + await async_math_contract.functions.increment().transact() + + output1 = await async_math_contract.functions.counter().call( + block_identifier=start_num.number + 6) + output2 = await async_math_contract.functions.counter().call( + block_identifier=start_num.number + 7) assert output1 == 1 assert output2 == 2 diff --git a/web3/eth.py b/web3/eth.py index ef1c134911..65829228c5 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -69,6 +69,8 @@ replace_transaction, ) from web3.contract import ( + AsyncContract, + AsyncContractCaller, ConciseContract, Contract, ContractCaller, @@ -116,6 +118,8 @@ class BaseEth(Module): _default_block: BlockIdentifier = "latest" _default_chain_id: Optional[int] = None gasPriceStrategy = None + defaultContractFactory: Type[Union[Contract, AsyncContract, + ConciseContract, ContractCaller, AsyncContractCaller]] = Contract _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, @@ -343,6 +347,30 @@ def call_munger( mungers=[default_root_munger] ) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Union[Type[Contract], Type[AsyncContract]]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Union[Contract, AsyncContract]: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[Contract], Contract, Type[AsyncContract], AsyncContract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + + def set_contract_factory( + self, contractFactory: Type[Union[Contract, AsyncContract, + ConciseContract, ContractCaller, AsyncContractCaller]] + ) -> None: + self.defaultContractFactory = contractFactory + class AsyncEth(BaseEth): is_async = True @@ -554,7 +582,6 @@ async def call( class Eth(BaseEth): account = Account() - defaultContractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] = Contract # noqa: E704,E501 iban = Iban def namereg(self) -> NoReturn: @@ -949,35 +976,12 @@ def filter_munger( mungers=[default_root_munger], ) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[Contract], Contract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - @deprecated_for("set_contract_factory") def setContractFactory( self, contractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] ) -> None: return self.set_contract_factory(contractFactory) - def set_contract_factory( - self, contractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] - ) -> None: - self.defaultContractFactory = contractFactory - def getCompilers(self) -> NoReturn: raise DeprecationWarning("This method has been deprecated as of EIP 1474.") From 0d723dd40dfcec7187a75978b2759632ea5a6bfa Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 11 Mar 2022 05:55:32 -0500 Subject: [PATCH 22/39] async_parse_block_identifier_int --- web3/contract.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 264f54ce97..317028ca08 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1373,7 +1373,7 @@ async def call( self._return_data_normalizers, self.function_identifier, call_transaction, - block_id, + block_id, # type: ignore self.contract_abi, self.abi, state_override, @@ -2029,11 +2029,11 @@ def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> Blo async def async_parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier - ) -> BlockIdentifier: + ) -> Awaitable[BlockIdentifier]: if isinstance(block_identifier, int): - return parse_block_identifier_int(w3, block_identifier) + return await async_parse_block_identifier_int(w3, block_identifier) elif block_identifier in ['latest', 'earliest', 'pending']: - return block_identifier + return block_identifier # type: ignore elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier): return await w3.eth.get_block(block_identifier)['number'] # type: ignore else: @@ -2051,6 +2051,18 @@ def parse_block_identifier_int(w3: 'Web3', block_identifier_int: int) -> BlockNu return BlockNumber(block_num) +async def async_parse_block_identifier_int(w3: 'Web3', block_identifier_int: int + ) -> Awaitable[BlockNumber]: + if block_identifier_int >= 0: + block_num = block_identifier_int + else: + last_block = await w3.eth.get_block('latest')['number'] # type: ignore + block_num = last_block + block_identifier_int + 1 + if block_num < 0: + raise BlockNumberOutofRange + return BlockNumber(block_num) # type: ignore + + def transact_with_contract_function( address: ChecksumAddress, w3: 'Web3', @@ -2176,7 +2188,7 @@ def async_find_functions_by_identifier( def get_function_by_identifier( fns: Sequence[ContractFunction], identifier: str -) -> ContractFunction: +) -> Union[ContractFunction, AsyncContractFunction]: if len(fns) > 1: raise ValueError( 'Found multiple functions with matching {0}. ' From 1bfda9f892470511d39042a59c18535550591be5 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Fri, 11 Mar 2022 16:05:08 -0500 Subject: [PATCH 23/39] Feature/asyncify contract (#2387) * asyncify eth.contract * Contract build_transaction method --- ens/main.py | 6 +- ethpm/package.py | 3 +- tests/core/contracts/conftest.py | 7 +- .../contracts/test_contract_call_interface.py | 28 ++++++++ web3/_utils/module_testing/web3_module.py | 2 +- web3/contract.py | 67 ++++++++++++------- web3/eth.py | 52 +++++++------- 7 files changed, 110 insertions(+), 55 deletions(-) diff --git a/ens/main.py b/ens/main.py index 928a3106a4..1a47b33844 100644 --- a/ens/main.py +++ b/ens/main.py @@ -262,7 +262,8 @@ def resolver(self, normal_name: str) -> Optional['Contract']: resolver_addr = self.ens.caller.resolver(normal_name_to_hash(normal_name)) if is_none_or_zero_address(resolver_addr): return None - return self._resolverContract(address=resolver_addr) + # TODO: look at possibly removing type ignore when AsyncENS is written + return self._resolverContract(address=resolver_addr) # type: ignore def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -393,7 +394,8 @@ def _set_resolver( namehash, resolver_addr ).transact(transact) - return self._resolverContract(address=resolver_addr) + # TODO: look at possibly removing type ignore when AsyncENS is written + return self._resolverContract(address=resolver_addr) # type: ignore def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 139c412614..5eeba64aeb 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -309,7 +309,8 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac contract_instance = self.w3.eth.contract( address=address, **contract_kwargs ) - return contract_instance + # TODO: type ignore may be able to be removed after more of AsynContract is finished + return contract_instance # type: ignore # # Build Dependencies diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index e066bd4f3e..196a2def19 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1052,16 +1052,15 @@ def buildTransaction(request): return functools.partial(invoke_contract, api_call_desig='buildTransaction') -@pytest_asyncio.fixture() -async def async_deploy(web3, Contract, apply_func=identity, args=None): +async def async_deploy(async_web3, Contract, apply_func=identity, args=None): args = args or [] deploy_txn = await Contract.constructor(*args).transact() - deploy_receipt = await web3.eth.wait_for_transaction_receipt(deploy_txn) + deploy_receipt = await async_web3.eth.wait_for_transaction_receipt(deploy_txn) assert deploy_receipt is not None address = apply_func(deploy_receipt['contractAddress']) contract = Contract(address=address) assert contract.address == address - assert len(await web3.eth.get_code(contract.address)) > 0 + assert len(await async_web3.eth.get_code(contract.address)) > 0 return contract diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 6b1c1809b8..44b7d9e5af 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -879,3 +879,31 @@ def test_call_revert_contract(revert_contract): # which does not contain the revert reason. Avoid that by giving a gas # value. revert_contract.functions.revertWithMessage().call({'gas': 100000}) + + +@pytest.mark.asyncio +async def test_async_call_with_no_arguments(async_math_contract, call): + result = await async_math_contract.functions.return13().call() + assert result == 13 + + +@pytest.mark.asyncio +async def test_async_call_with_one_argument(async_math_contract, call): + result = await async_math_contract.functions.multiply7(3).call() + assert result == 21 + + +@pytest.mark.asyncio +async def test_async_returns_data_from_specified_block(async_w3, async_math_contract): + start_num = await async_w3.eth.get_block('latest') + await async_w3.provider.make_request(method='evm_mine', params=[5]) + await async_math_contract.functions.increment().transact() + await async_math_contract.functions.increment().transact() + + output1 = await async_math_contract.functions.counter().call( + block_identifier=start_num.number + 6) + output2 = await async_math_contract.functions.counter().call( + block_identifier=start_num.number + 7) + + assert output1 == 1 + assert output2 == 2 diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py index fbe562bff4..73abb9cbfb 100644 --- a/web3/_utils/module_testing/web3_module.py +++ b/web3/_utils/module_testing/web3_module.py @@ -179,7 +179,7 @@ def test_solidityKeccak( self, w3: "Web3", types: Sequence[TypeStr], values: Sequence[Any], expected: HexBytes ) -> None: if isinstance(expected, type) and issubclass(expected, Exception): - with pytest.raises(expected): + with pytest.raises(expected): # type: ignore w3.solidityKeccak(types, values) return diff --git a/web3/contract.py b/web3/contract.py index 9d661ca02d..317028ca08 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -840,25 +840,10 @@ def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: return transact_transaction @combomethod - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - """ - Build the transaction dictionary without sending - """ - - if transaction is None: - built_transaction: TxParams = {} - else: - built_transaction = cast(TxParams, dict(**transaction)) - self.check_forbidden_keys_in_transaction(built_transaction, - ["data", "to"]) - - if self.w3.eth.default_account is not empty: - # type ignored b/c check prevents an empty default_account - built_transaction.setdefault('from', self.w3.eth.default_account) # type: ignore - - built_transaction['data'] = self.data_in_transaction + def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + built_transaction = self._get_transaction(transaction) built_transaction['to'] = Address(b'') - return fill_transaction_defaults(self.w3, built_transaction) + return built_transaction @staticmethod def check_forbidden_keys_in_transaction( @@ -877,6 +862,22 @@ class ContractConstructor(BaseContractConstructor): def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return self.w3.eth.send_transaction(self._get_transaction(transaction)) + @combomethod + def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + built_transaction = self._build_transaction(transaction) + return fill_transaction_defaults(self.w3, built_transaction) + + @combomethod + @deprecated_for("build_transaction") + def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + return self.build_transaction(transaction) + class AsyncContractConstructor(BaseContractConstructor): @@ -885,6 +886,14 @@ async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return await self.w3.eth.send_transaction( # type: ignore self._get_transaction(transaction)) + @combomethod + async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + """ + Build the transaction dictionary without sending + """ + built_transaction = self._build_transaction(transaction) + return fill_transaction_defaults(self.w3, built_transaction) + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} @@ -1364,7 +1373,7 @@ async def call( self._return_data_normalizers, self.function_identifier, call_transaction, - block_id, + block_id, # type: ignore self.contract_abi, self.abi, state_override, @@ -2020,11 +2029,11 @@ def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> Blo async def async_parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier - ) -> BlockIdentifier: + ) -> Awaitable[BlockIdentifier]: if isinstance(block_identifier, int): - return parse_block_identifier_int(w3, block_identifier) + return await async_parse_block_identifier_int(w3, block_identifier) elif block_identifier in ['latest', 'earliest', 'pending']: - return block_identifier + return block_identifier # type: ignore elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier): return await w3.eth.get_block(block_identifier)['number'] # type: ignore else: @@ -2042,6 +2051,18 @@ def parse_block_identifier_int(w3: 'Web3', block_identifier_int: int) -> BlockNu return BlockNumber(block_num) +async def async_parse_block_identifier_int(w3: 'Web3', block_identifier_int: int + ) -> Awaitable[BlockNumber]: + if block_identifier_int >= 0: + block_num = block_identifier_int + else: + last_block = await w3.eth.get_block('latest')['number'] # type: ignore + block_num = last_block + block_identifier_int + 1 + if block_num < 0: + raise BlockNumberOutofRange + return BlockNumber(block_num) # type: ignore + + def transact_with_contract_function( address: ChecksumAddress, w3: 'Web3', @@ -2167,7 +2188,7 @@ def async_find_functions_by_identifier( def get_function_by_identifier( fns: Sequence[ContractFunction], identifier: str -) -> ContractFunction: +) -> Union[ContractFunction, AsyncContractFunction]: if len(fns) > 1: raise ValueError( 'Found multiple functions with matching {0}. ' diff --git a/web3/eth.py b/web3/eth.py index ef1c134911..65829228c5 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -69,6 +69,8 @@ replace_transaction, ) from web3.contract import ( + AsyncContract, + AsyncContractCaller, ConciseContract, Contract, ContractCaller, @@ -116,6 +118,8 @@ class BaseEth(Module): _default_block: BlockIdentifier = "latest" _default_chain_id: Optional[int] = None gasPriceStrategy = None + defaultContractFactory: Type[Union[Contract, AsyncContract, + ConciseContract, ContractCaller, AsyncContractCaller]] = Contract _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, @@ -343,6 +347,30 @@ def call_munger( mungers=[default_root_munger] ) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Union[Type[Contract], Type[AsyncContract]]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Union[Contract, AsyncContract]: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[Contract], Contract, Type[AsyncContract], AsyncContract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + + def set_contract_factory( + self, contractFactory: Type[Union[Contract, AsyncContract, + ConciseContract, ContractCaller, AsyncContractCaller]] + ) -> None: + self.defaultContractFactory = contractFactory + class AsyncEth(BaseEth): is_async = True @@ -554,7 +582,6 @@ async def call( class Eth(BaseEth): account = Account() - defaultContractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] = Contract # noqa: E704,E501 iban = Iban def namereg(self) -> NoReturn: @@ -949,35 +976,12 @@ def filter_munger( mungers=[default_root_munger], ) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[Contract], Contract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - @deprecated_for("set_contract_factory") def setContractFactory( self, contractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] ) -> None: return self.set_contract_factory(contractFactory) - def set_contract_factory( - self, contractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] - ) -> None: - self.defaultContractFactory = contractFactory - def getCompilers(self) -> NoReturn: raise DeprecationWarning("This method has been deprecated as of EIP 1474.") From a0dad2d091d8ae59d26fb1c8275ad823e02d9fcf Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Mon, 14 Mar 2022 14:02:25 -0600 Subject: [PATCH 24/39] pull master into asyncify-contract (#2389) --- Dockerfile | 3 +- docker-compose.yml | 2 - docs/conf.py | 16 +- docs/examples.rst | 15 +- docs/v4_migration.rst | 4 +- docs/web3.eth.account.rst | 55 ++++++- docs/web3.eth.rst | 4 +- ens/main.py | 15 +- ens/utils.py | 2 +- ethpm/contract.py | 12 +- ethpm/tools/builder.py | 2 +- ethpm/validation/manifest.py | 12 +- newsfragments/2369.misc.rst | 1 + newsfragments/2372.misc.rst | 6 + newsfragments/2376.bugfix.rst | 1 + newsfragments/2380.doc.rst | 1 + newsfragments/2383.breaking-change.rst | 1 + tests/core/contracts/conftest.py | 2 +- .../test_contract_ambiguous_functions.py | 2 +- .../contracts/test_contract_call_interface.py | 2 +- ...st_contract_method_to_argument_matching.py | 6 +- tests/core/eth-module/test_eth_properties.py | 29 ++++ .../test_filter_against_transaction_logs.py | 12 +- tests/core/method-class/test_method.py | 137 ++++++++++++++---- .../middleware/test_fixture_middleware.py | 5 +- .../test_simple_cache_middleware.py | 2 +- .../test_time_based_cache_middleware.py | 2 +- .../middleware/test_transaction_signing.py | 5 +- tests/core/module-class/test_module.py | 78 ++++++++++ tests/core/pm-module/test_ens_integration.py | 2 +- tests/core/providers/test_auto_provider.py | 8 +- tests/core/providers/test_ipc_provider.py | 2 +- .../core/providers/test_websocket_provider.py | 4 +- .../test_abi_filtering_by_argument_name.py | 14 +- tests/core/utilities/test_formatters.py | 2 +- tests/ens/conftest.py | 2 +- tests/ethpm/_utils/test_contract_utils.py | 10 +- tests/ethpm/validation/test_manifest.py | 6 +- tests/integration/generate_fixtures/common.py | 14 +- .../generate_fixtures/go_ethereum.py | 7 +- tests/integration/go_ethereum/conftest.py | 7 +- .../go_ethereum/test_goethereum_http.py | 2 +- .../go_ethereum/test_goethereum_ws.py | 2 +- web3/__init__.py | 1 - web3/_utils/abi.py | 48 +++--- web3/_utils/admin.py | 10 +- web3/_utils/async_transactions.py | 4 +- web3/_utils/blocks.py | 4 +- web3/_utils/caching.py | 7 +- web3/_utils/contracts.py | 21 +-- web3/_utils/datatypes.py | 6 +- web3/_utils/decorators.py | 2 +- web3/_utils/empty.py | 3 - web3/_utils/encoding.py | 24 +-- web3/_utils/ens.py | 2 +- web3/_utils/events.py | 9 +- web3/_utils/filters.py | 2 +- web3/_utils/http.py | 5 +- web3/_utils/math.py | 5 +- web3/_utils/method_formatters.py | 6 +- web3/_utils/miner.py | 6 +- web3/_utils/module_testing/eth_module.py | 4 +- .../go_ethereum_admin_module.py | 2 +- web3/_utils/normalizers.py | 17 +-- web3/_utils/personal.py | 4 +- web3/_utils/rpc_abi.py | 4 +- web3/_utils/threads.py | 2 +- web3/_utils/transactions.py | 8 +- web3/_utils/txpool.py | 6 +- web3/_utils/validation.py | 19 +-- web3/auto/infura/endpoints.py | 8 +- web3/contract.py | 32 ++-- web3/datastructures.py | 10 +- web3/eth.py | 31 ++-- web3/exceptions.py | 8 +- web3/main.py | 12 +- web3/manager.py | 6 +- web3/method.py | 77 ++++++---- web3/middleware/filter.py | 8 +- web3/middleware/signing.py | 2 +- web3/middleware/stalecheck.py | 4 +- web3/middleware/validation.py | 17 +-- web3/module.py | 12 ++ web3/parity.py | 6 +- web3/providers/async_rpc.py | 10 +- web3/providers/auto.py | 11 +- web3/providers/eth_tester/defaults.py | 7 +- web3/providers/ipc.py | 15 +- web3/providers/rpc.py | 10 +- web3/providers/websocket.py | 12 +- web3/tools/benchmark/node.py | 2 +- web3/tools/pytest_ethereum/linker.py | 7 +- 92 files changed, 628 insertions(+), 426 deletions(-) create mode 100644 newsfragments/2369.misc.rst create mode 100644 newsfragments/2372.misc.rst create mode 100644 newsfragments/2376.bugfix.rst create mode 100644 newsfragments/2380.doc.rst create mode 100644 newsfragments/2383.breaking-change.rst create mode 100644 tests/core/module-class/test_module.py diff --git a/Dockerfile b/Dockerfile index 912a2961e2..8fe1aabb26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ -FROM python:3.6 +FROM python:3.9 # Set up code directory -RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Install Linux dependencies diff --git a/docker-compose.yml b/docker-compose.yml index c130de201a..2ffdfaede2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,6 @@ services: sandbox: build: context: . - environment: - PARITY_VERSION: v2.3.5 volumes: - .:/code command: tail -f /dev/null diff --git a/docs/conf.py b/docs/conf.py index fd50c0c203..a4f5ec9031 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,8 +52,8 @@ master_doc = 'index' # General information about the project. -project = u'Web3.py' -copyright = u'2018, Piper Merriam, Jason Carver' +project = 'Web3.py' +copyright = '2018, Piper Merriam, Jason Carver' __version__ = setup_version # The version info for the project you're documenting, acts as replacement for @@ -222,8 +222,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'Populus.tex', u'Populus Documentation', - u'Piper Merriam', 'manual'), + ('index', 'Populus.tex', 'Populus Documentation', + 'Piper Merriam', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -252,8 +252,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'web3', u'Web3.py Documentation', - [u'Piper Merriam'], 1) + ('index', 'web3', 'Web3.py Documentation', + ['Piper Merriam'], 1) ] # If true, show URL addresses after external links. @@ -266,8 +266,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Web3.py', u'Web3.py Documentation', - u'Piper Merriam', 'Web3.py', 'Backend agnostic Ethereum client interactions.', + ('index', 'Web3.py', 'Web3.py Documentation', + 'Piper Merriam', 'Web3.py', 'Backend agnostic Ethereum client interactions.', 'Miscellaneous'), ] diff --git a/docs/examples.rst b/docs/examples.rst index e54ed70d43..83d41a6ee7 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -992,7 +992,7 @@ The script can be run with: ``python ./eventscanner.py `` # from our in-memory cache block_when = get_block_when(block_number) - logger.debug("Processing event %s, block:%d count:%d", evt["event"], evt["blockNumber"]) + logger.debug(f"Processing event {evt["event"]}, block: {evt["blockNumber"]} count: {evt["blockNumber"]}") processed = self.state.process_event(block_when, evt) all_processed.append(processed) @@ -1064,8 +1064,8 @@ The script can be run with: ``python ./eventscanner.py `` # Print some diagnostics to logs to try to fiddle with real world JSON-RPC API performance estimated_end_block = current_block + chunk_size logger.debug( - "Scanning token transfers for blocks: %d - %d, chunk size %d, last chunk scan took %f, last logs found %d", - current_block, estimated_end_block, chunk_size, last_scan_duration, last_logs_found) + f"Scanning token transfers for blocks: {current_block} - {estimated_end_block}, chunk size {chunk_size}, last chunk scan took {last_scan_duration}, last logs found {last_logs_found}" + ) start = time.time() actual_end_block, end_block_timestamp, new_entries = self.scan_chunk(current_block, estimated_end_block) @@ -1116,12 +1116,7 @@ The script can be run with: ``python ./eventscanner.py `` if i < retries - 1: # Give some more verbose info than the default middleware logger.warning( - "Retrying events for block range %d - %d (%d) failed with %s, retrying in %s seconds", - start_block, - end_block, - end_block-start_block, - e, - delay) + f"Retrying events for block range {start_block} - {end_block} ({end_block-start_block}) failed with {e} , retrying in {delay} seconds") # Decrease the `eth_getBlocks` range end_block = start_block + ((end_block - start_block) // 2) # Let the JSON-RPC to recover e.g. from restart @@ -1175,7 +1170,7 @@ The script can be run with: ``python ./eventscanner.py `` toBlock=to_block ) - logger.debug("Querying eth_getLogs with the following parameters: %s", event_filter_params) + logger.debug(f"Querying eth_getLogs with the following parameters: {event_filter_params}") # Call JSON-RPC API on your Ethereum node. # get_logs() returns raw AttributedDict entries diff --git a/docs/v4_migration.rst b/docs/v4_migration.rst index 14ca20f2e1..f7c2a4ceed 100644 --- a/docs/v4_migration.rst +++ b/docs/v4_migration.rst @@ -68,7 +68,7 @@ printing out new block hashes as they appear: .. code-block:: python >>> def new_block_callback(block_hash): - ... print "New Block: {0}".format(block_hash) + ... print(f"New Block: {block_hash}") ... >>> new_block_filter = web3.eth.filter('latest') >>> new_block_filter.watch(new_block_callback) @@ -79,7 +79,7 @@ In v4, that same logic: >>> new_block_filter = web3.eth.filter('latest') >>> for block_hash in new_block_filter.get_new_entries(): - ... print("New Block: {}".format(block_hash)) + ... print(f"New Block: {block_hash}") The caller is responsible for polling the results from ``get_new_entries()``. See :ref:`asynchronous_filters` for examples of filter-event handling with web3 v4. diff --git a/docs/web3.eth.account.rst b/docs/web3.eth.account.rst index 60a25839f2..a331c3b733 100644 --- a/docs/web3.eth.account.rst +++ b/docs/web3.eth.account.rst @@ -39,7 +39,7 @@ Hosted Private Key not your Ether" in the wise words of Andreas Antonopoulos. Some Common Uses for Local Private Keys -------------------------------------------- +--------------------------------------- A very common reason to work with local private keys is to interact with a hosted node. @@ -55,6 +55,59 @@ Using private keys usually involves ``w3.eth.account`` in one way or another. Re or see a full list of things you can do in the docs for :class:`eth_account.Account `. +Read a private key from an environment variable +----------------------------------------------- + +In this example we pass the private key to our Python application in an +`environment variable `_. +This private key is then added to the transaction signing keychain +with ``Signing`` middleware. + +If unfamiliar, note that you can `export your private keys from Metamask and other wallets `_. + +.. warning :: + + - **Never** share your private keys. + - **Never** put your private keys in source code. + - **Never** commit private keys to a Git repository. + +Example ``account_test_script.py`` + +.. code-block:: python + + import os + from eth_account import Account + from eth_account.signers.local import LocalAccount + from web3.auto import w3 + from web3.middleware import construct_sign_and_send_raw_middleware + + private_key = os.environ.get("PRIVATE_KEY") + assert private_key is not None, "You must set PRIVATE_KEY environment variable" + assert private_key.startswith("0x"), "Private key must start with 0x hex prefix" + + account: LocalAccount = Account.from_key(private_key) + w3.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) + + print(f"Your hot wallet address is {account.address}") + +Example how to run this in UNIX shell: + +.. code-block:: shell + + # Generate a new 256-bit random integer using openssl UNIX command that acts as a private key. + # You can also do: + # python -c "from web3 import Web3; w3 = Web3(); acc = w3.eth.account.create(); print(f'private key={w3.toHex(acc.key)}, account={acc.address}')" + # Store this in a safe place, like in your password manager. + export PRIVATE_KEY=0x`openssl rand -hex 32` + + # Run our script + python account_test_script.py + +This will print:: + + Your hot wallet address is 0x27C8F899bb69E1501BBB96d09d7477a2a7518918 + + .. _extract_geth_pk: Extract private key from geth keyfile diff --git a/docs/web3.eth.rst b/docs/web3.eth.rst index eaa1653782..9fbe9c13ab 100644 --- a/docs/web3.eth.rst +++ b/docs/web3.eth.rst @@ -364,7 +364,7 @@ The following methods are available on the ``web3.eth`` namespace. assert rlp_account == HexaryTrie.get_from_proof( root, trie_key, format_proof_nodes(proof.accountProof) - ), "Failed to verify account proof {}".format(proof.address) + ), f"Failed to verify account proof {proof.address}" for storage_proof in proof.storageProof: trie_key = keccak(pad_bytes(b'\x00', 32, storage_proof.key)) @@ -376,7 +376,7 @@ The following methods are available on the ``web3.eth`` namespace. assert rlp_value == HexaryTrie.get_from_proof( root, trie_key, format_proof_nodes(storage_proof.proof) - ), "Failed to verify storage proof {}".format(storage_proof.key) + ), f"Failed to verify storage proof {storage_proof.key}" return True diff --git a/ens/main.py b/ens/main.py index 1a47b33844..245cc56180 100644 --- a/ens/main.py +++ b/ens/main.py @@ -227,10 +227,10 @@ def setup_name( address = resolved elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX: raise AddressMismatch( - "Could not set address %r to point to name, because the name resolves to %r. " - "To change the name for an existing address, call setup_address() first." % ( - address, resolved - ) + f"Could not set address {address!r} to point to name, " + f"because the name resolves to {resolved!r}. " + "To change the name for an existing address, call " + "setup_address() first." ) if is_none_or_zero_address(address): address = self.owner(name) @@ -336,9 +336,8 @@ def _assert_control(self, account: ChecksumAddress, name: str, parent_owned: Optional[str] = None) -> None: if not address_in(account, self.w3.eth.accounts): raise UnauthorizedError( - "in order to modify %r, you must control account %r, which owns %r" % ( - name, account, parent_owned or name - ) + f"in order to modify {name!r}, you must control account" + f" {account!r}, which owns {parent_owned or name!r}" ) def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]: @@ -375,7 +374,7 @@ def _claim_ownership( label_to_hash(label), owner ).transact(transact) - owned = "%s.%s" % (label, owned) + owned = f"{label}.{owned}" def _set_resolver( self, diff --git a/ens/utils.py b/ens/utils.py index 43c223b5e2..33fda9ee29 100644 --- a/ens/utils.py +++ b/ens/utils.py @@ -140,7 +140,7 @@ def sha3_text(val: Union[str, bytes]) -> HexBytes: def label_to_hash(label: str) -> HexBytes: label = normalize_name(label) if '.' in label: - raise ValueError("Cannot generate hash for label %r with a '.'" % label) + raise ValueError(f"Cannot generate hash for label {label!r} with a '.'") return Web3().keccak(text=label) diff --git a/ethpm/contract.py b/ethpm/contract.py index d9b226d392..e56d6670c9 100644 --- a/ethpm/contract.py +++ b/ethpm/contract.py @@ -56,7 +56,7 @@ def __init__(self, address: bytes, **kwargs: Any) -> None: ) validate_address(address) # type ignored to allow for undefined **kwargs on `Contract` base class __init__ - super(LinkableContract, self).__init__(address=address, **kwargs) # type: ignore + super().__init__(address=address, **kwargs) # type: ignore @classmethod def factory( @@ -69,7 +69,7 @@ def factory( if not is_prelinked_bytecode(to_bytes(hexstr=bytecode), dep_link_refs): needs_bytecode_linking = True kwargs = assoc(kwargs, "needs_bytecode_linking", needs_bytecode_linking) - return super(LinkableContract, cls).factory(w3, class_name, **kwargs) + return super().factory(w3, class_name, **kwargs) @classmethod def constructor(cls, *args: Any, **kwargs: Any) -> ContractConstructor: @@ -77,7 +77,7 @@ def constructor(cls, *args: Any, **kwargs: Any) -> ContractConstructor: raise BytecodeLinkingError( "Contract cannot be deployed until its bytecode is linked." ) - return super(LinkableContract, cls).constructor(*args, **kwargs) + return super().constructor(*args, **kwargs) @classmethod def link_bytecode(cls, attr_dict: Dict[str, str]) -> Type["LinkableContract"]: @@ -111,7 +111,7 @@ def validate_attr_dict(self, attr_dict: Dict[str, str]) -> None: """ Validates that ContractType keys in attr_dict reference existing manifest ContractTypes. """ - attr_dict_names = list(attr_dict.keys()) + attr_dict_names = attr_dict.keys() if not self.unlinked_references and not self.linked_references: raise BytecodeLinkingError( @@ -122,8 +122,8 @@ def validate_attr_dict(self, attr_dict: Dict[str, str]) -> None: linked_refs = self.linked_references or ({},) all_link_refs = unlinked_refs + linked_refs - all_link_names = [ref["name"] for ref in all_link_refs if ref] - if set(attr_dict_names) != set(all_link_names): + all_link_names = {ref["name"] for ref in all_link_refs if ref} + if attr_dict_names != all_link_names: raise BytecodeLinkingError( "All link references must be defined when calling " "`link_bytecode` on a contract factory." diff --git a/ethpm/tools/builder.py b/ethpm/tools/builder.py index 895b790c09..6f38968402 100644 --- a/ethpm/tools/builder.py +++ b/ethpm/tools/builder.py @@ -498,7 +498,7 @@ def normalize_compiler_output(compiler_output: Dict[str, Any]) -> Dict[str, Any] ] paths, names = zip(*paths_and_names) if len(names) != len(set(names)): - duplicates = set([name for name in names if names.count(name) > 1]) + duplicates = {name for name in names if names.count(name) > 1} raise ManifestBuildingError( f"Duplicate contract types: {duplicates} were found in the compiler output." ) diff --git a/ethpm/validation/manifest.py b/ethpm/validation/manifest.py index 6aba1b1034..eb94f86523 100644 --- a/ethpm/validation/manifest.py +++ b/ethpm/validation/manifest.py @@ -66,11 +66,11 @@ def _load_schema_data() -> Dict[str, Any]: def extract_contract_types_from_deployments(deployment_data: List[Any]) -> Set[str]: - contract_types = set( + contract_types = { deployment["contractType"] for chain_deployments in deployment_data for deployment in chain_deployments.values() - ) + } return contract_types @@ -108,11 +108,11 @@ def validate_manifest_deployments(manifest: Dict[str, Any]) -> None: """ Validate that a manifest's deployments contracts reference existing contractTypes. """ - if set(("contractTypes", "deployments")).issubset(manifest): - all_contract_types = list(manifest["contractTypes"].keys()) - all_deployments = list(manifest["deployments"].values()) + if {"contractTypes", "deployments"}.issubset(manifest): + all_contract_types = manifest["contractTypes"].keys() + all_deployments = manifest["deployments"].values() all_deployment_names = extract_contract_types_from_deployments(all_deployments) - missing_contract_types = set(all_deployment_names).difference( + missing_contract_types = all_deployment_names.difference( all_contract_types ) if missing_contract_types: diff --git a/newsfragments/2369.misc.rst b/newsfragments/2369.misc.rst new file mode 100644 index 0000000000..cede2588df --- /dev/null +++ b/newsfragments/2369.misc.rst @@ -0,0 +1 @@ +bump docker base image to ``3.9`` and remove parity tests from docker. \ No newline at end of file diff --git a/newsfragments/2372.misc.rst b/newsfragments/2372.misc.rst new file mode 100644 index 0000000000..bd2a98be31 --- /dev/null +++ b/newsfragments/2372.misc.rst @@ -0,0 +1,6 @@ +improve the code base by: +- using `f-strings`. +- removing redundant `set()` calls. +- making error messages more readable in the code. +- removing python2 `u` prefix. +- other cleanups. \ No newline at end of file diff --git a/newsfragments/2376.bugfix.rst b/newsfragments/2376.bugfix.rst new file mode 100644 index 0000000000..8518a5427e --- /dev/null +++ b/newsfragments/2376.bugfix.rst @@ -0,0 +1 @@ +Add async default_chain_id and chain_id setter diff --git a/newsfragments/2380.doc.rst b/newsfragments/2380.doc.rst new file mode 100644 index 0000000000..6baf4bb2d6 --- /dev/null +++ b/newsfragments/2380.doc.rst @@ -0,0 +1 @@ +Document reading private keys from environment variables diff --git a/newsfragments/2383.breaking-change.rst b/newsfragments/2383.breaking-change.rst new file mode 100644 index 0000000000..a53354bbc7 --- /dev/null +++ b/newsfragments/2383.breaking-change.rst @@ -0,0 +1 @@ +Add ``attach_methods()`` to ``Module`` class to facilitate attaching methods to modules. \ No newline at end of file diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 196a2def19..ab9d5abd12 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1024,7 +1024,7 @@ def invoke_contract(api_call_desig='call', tx_params={}): allowable_call_desig = ['call', 'transact', 'estimateGas', 'buildTransaction'] if api_call_desig not in allowable_call_desig: - raise ValueError("allowable_invoke_method must be one of: %s" % allowable_call_desig) + raise ValueError(f"allowable_invoke_method must be one of: {allowable_call_desig}") function = contract.functions[contract_function] result = getattr(function(*func_args, **func_kwargs), api_call_desig)(tx_params) diff --git a/tests/core/contracts/test_contract_ambiguous_functions.py b/tests/core/contracts/test_contract_ambiguous_functions.py index 69e15e4ff6..8f6c0b5ae2 100644 --- a/tests/core/contracts/test_contract_ambiguous_functions.py +++ b/tests/core/contracts/test_contract_ambiguous_functions.py @@ -153,7 +153,7 @@ def test_find_or_get_functions_by_type(w3, method, args, repr_func, expected): ( 'get_function_by_selector', (b'\x00' * (4 + 1), ), - r'expected value of size 4 bytes. Got: %s bytes' % (4 + 1), + f'expected value of size 4 bytes. Got: {(4 + 1)} bytes', ValueError ), ( diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 44b7d9e5af..1b87b48f7d 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -726,7 +726,7 @@ def test_reflect_fixed_value(fixed_reflection_contract, function, value): ('reflect_short_u', Decimal('0.01'), "no matching argument types"), ( 'reflect_short_u', - Decimal('1e-%d' % (DEFAULT_DECIMALS + 1)), + Decimal(f'1e-{DEFAULT_DECIMALS + 1}'), "no matching argument types", ), ('reflect_short_u', Decimal('25.4' + '9' * DEFAULT_DECIMALS), "no matching argument types"), diff --git a/tests/core/contracts/test_contract_method_to_argument_matching.py b/tests/core/contracts/test_contract_method_to_argument_matching.py index 8f80dbf6c6..d4c051b2a0 100644 --- a/tests/core/contracts/test_contract_method_to_argument_matching.py +++ b/tests/core/contracts/test_contract_method_to_argument_matching.py @@ -150,7 +150,7 @@ def test_finds_function_with_matching_args(w3, arguments, expected_types): abi = Contract._find_matching_fn_abi('a', arguments) assert abi['name'] == 'a' assert len(abi['inputs']) == len(expected_types) - assert set(get_abi_input_types(abi)) == set(expected_types) + assert get_abi_input_types(abi) == expected_types def test_finds_function_with_matching_args_deprecation_warning(w3): @@ -160,7 +160,7 @@ def test_finds_function_with_matching_args_deprecation_warning(w3): abi = Contract._find_matching_fn_abi('a', ['']) assert abi['name'] == 'a' assert len(abi['inputs']) == len(['bytes32']) - assert set(get_abi_input_types(abi)) == set(['bytes32']) + assert get_abi_input_types(abi) == ['bytes32'] def test_error_when_duplicate_match(w3): @@ -193,4 +193,4 @@ def test_strict_finds_function_with_matching_args(w3_strict_abi, arguments, expe abi = Contract._find_matching_fn_abi('a', arguments) assert abi['name'] == 'a' assert len(abi['inputs']) == len(expected_types) - assert set(get_abi_input_types(abi)) == set(expected_types) + assert get_abi_input_types(abi) == expected_types diff --git a/tests/core/eth-module/test_eth_properties.py b/tests/core/eth-module/test_eth_properties.py index 596222b62a..c24070eb60 100644 --- a/tests/core/eth-module/test_eth_properties.py +++ b/tests/core/eth-module/test_eth_properties.py @@ -1,5 +1,23 @@ import pytest +from web3 import Web3 +from web3.eth import ( + AsyncEth, +) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) + + +@pytest.fixture +def async_w3(): + return Web3( + AsyncEthereumTesterProvider(), + middlewares=[], + modules={ + 'eth': (AsyncEth,), + }) + def test_eth_protocol_version(w3): with pytest.warns(DeprecationWarning): @@ -28,3 +46,14 @@ def test_set_chain_id(w3): w3.eth.chain_id = None assert w3.eth.chain_id == 61 + + +@pytest.mark.asyncio +async def test_async_set_chain_id(async_w3): + assert await async_w3.eth.chain_id == 61 + + async_w3.eth.chain_id = 72 + assert await async_w3.eth.chain_id == 72 + + async_w3.eth.chain_id = None + assert await async_w3.eth.chain_id == 61 diff --git a/tests/core/filtering/test_filter_against_transaction_logs.py b/tests/core/filtering/test_filter_against_transaction_logs.py index 664ec64b95..d1e63a4e43 100644 --- a/tests/core/filtering/test_filter_against_transaction_logs.py +++ b/tests/core/filtering/test_filter_against_transaction_logs.py @@ -22,8 +22,8 @@ def test_sync_filter_against_log_events(w3_empty, txn_filter = w3.eth.filter({}) - txn_hashes = [] - txn_hashes.append(emitter.functions.logNoArgs(emitter_event_ids.LogNoArguments).transact()) + txn_hashes = set() + txn_hashes.add(emitter.functions.logNoArgs(emitter_event_ids.LogNoArguments).transact()) for txn_hash in txn_hashes: wait_for_transaction(w3, txn_hash) @@ -34,7 +34,7 @@ def test_sync_filter_against_log_events(w3_empty, seen_logs = txn_filter.get_new_entries() - assert set(txn_hashes) == set(log['transactionHash'] for log in seen_logs) + assert txn_hashes == {log['transactionHash'] for log in seen_logs} @pytest.mark.skip(reason="fixture 'w3_empty' not found") @@ -51,9 +51,9 @@ def test_async_filter_against_log_events(w3_empty, txn_filter = w3.eth.filter({}) txn_filter.watch(seen_logs.append) - txn_hashes = [] + txn_hashes = set() - txn_hashes.append(emitter.functions.logNoArgs(emitter_event_ids.LogNoArguments).transact()) + txn_hashes.add(emitter.functions.logNoArgs(emitter_event_ids.LogNoArguments).transact()) for txn_hash in txn_hashes: wait_for_transaction(w3, txn_hash) @@ -64,4 +64,4 @@ def test_async_filter_against_log_events(w3_empty, txn_filter.stop_watching(30) - assert set(txn_hashes) == set(log['transactionHash'] for log in seen_logs) + assert txn_hashes == {log['transactionHash'] for log in seen_logs} diff --git a/tests/core/method-class/test_method.py b/tests/core/method-class/test_method.py index 28b7f953aa..9e0bb64e0d 100644 --- a/tests/core/method-class/test_method.py +++ b/tests/core/method-class/test_method.py @@ -3,6 +3,9 @@ ) import pytest +from eth_utils import ( + ValidationError, +) from eth_utils.toolz import ( compose, ) @@ -32,7 +35,7 @@ def test_method_accepts_callable_for_selector(): def test_method_selector_fn_accepts_str(): method = Method( - mungers=None, + is_property=True, json_rpc_method='eth_method', ) assert method.method_selector_fn() == 'eth_method' @@ -69,7 +72,7 @@ def test_get_formatters_non_falsy_config_retrieval(): first_formatter = (method.request_formatters(method_name).first,) all_other_formatters = method.request_formatters(method_name).funcs assert len(first_formatter + all_other_formatters) == 2 - # assert method.request_formatters('eth_nonmatching') == 'nonmatch' + assert (method.request_formatters('eth_getBalance').first,) == first_formatter def test_input_munger_parameter_passthrough_matching_arity(): @@ -77,7 +80,7 @@ def test_input_munger_parameter_passthrough_matching_arity(): mungers=[lambda m, z, y: ['success']], json_rpc_method='eth_method', ) - method.input_munger(object(), ['first', 'second'], {}) == 'success' + assert method.input_munger(object(), ['first', 'second'], {}) == ['success'] def test_input_munger_parameter_passthrough_mismatch_arity(): @@ -89,25 +92,87 @@ def test_input_munger_parameter_passthrough_mismatch_arity(): method.input_munger(object(), ['first', 'second', 'third'], {}) -def test_input_munger_falsy_config_result_in_default_munger(): +def test_default_input_munger_with_no_input_parameters(): method = Method( - mungers=[], json_rpc_method='eth_method', ) - method.input_munger(object(), [], {}) == [] + assert method.input_munger(object(), [], {}) == [] -def test_default_input_munger_with_input_parameters_exception(): +@pytest.mark.parametrize('empty', ([], (), None), ids=['empty-list', 'empty-tuple', 'None']) +def test_empty_input_munger_with_no_input_parameters(empty): method = Method( - mungers=[], + mungers=empty, json_rpc_method='eth_method', ) - with pytest.raises(TypeError): + assert method.input_munger(object(), [], {}) == [] + + +def test_default_input_munger_with_input_parameters(): + method = Method( + json_rpc_method='eth_method', + ) + assert method.input_munger(object(), [1], {}) == [1] + + +@pytest.mark.parametrize('empty', ([], (), None), ids=['empty-list', 'empty-tuple', 'None']) +def test_empty_input_mungers_with_input_parameters(empty): + method = Method( + mungers=empty, + json_rpc_method='eth_method', + ) + assert method.input_munger(object(), [1], {}) == [1] + + +def test_default_munger_for_property_with_no_input_parameters(): + method = Method( + is_property=True, + json_rpc_method='eth_method', + ) + assert method.input_munger(object(), [], {}) == () + + +@pytest.mark.parametrize('empty', ([], (), None), ids=['empty-list', 'empty-tuple', 'None']) +def test_empty_mungers_for_property_with_no_input_parameters(empty): + method = Method( + is_property=True, + mungers=empty, + json_rpc_method='eth_method', + ) + assert method.input_munger(object(), [], {}) == () + + +def test_default_munger_for_property_with_input_parameters_raises_ValidationError(): + method = Method( + is_property=True, + json_rpc_method='eth_method', + ) + with pytest.raises(ValidationError, match='Parameters cannot be passed to a property'): method.input_munger(object(), [1], {}) +@pytest.mark.parametrize('empty', ([], (), None), ids=['empty-list', 'empty-tuple', 'None']) +def test_empty_mungers_for_property_with_input_parameters_raises_ValidationError(empty): + method = Method( + is_property=True, + mungers=empty, + json_rpc_method='eth_method', + ) + with pytest.raises(ValidationError, match='Parameters cannot be passed to a property'): + method.input_munger(object(), [1], {}) + + +def test_property_with_mungers_raises_ValidationError(): + with pytest.raises(ValidationError, match='Mungers cannot be used with a property'): + Method( + is_property=True, + mungers=[lambda m, z, y: 'success'], + json_rpc_method='eth_method', + ) + + @pytest.mark.parametrize( - "method_config,args,kwargs,expected_request_result,expected_result_formatters_len", + "method_config,args,kwargs,expected_request_result", ( ( { @@ -116,17 +181,15 @@ def test_default_input_munger_with_input_parameters_exception(): [], {}, ValueError, - 2 ), ( { 'mungers': [], 'json_rpc_method': 'eth_getBalance', }, - ['unexpected_argument'], + ['only_the_first_argument_but_expects_two'], {}, - TypeError, - 2 + IndexError, ), ( { @@ -136,7 +199,6 @@ def test_default_input_munger_with_input_parameters_exception(): ['0x0000000000000000000000000000000000000000', 3], {}, ('eth_getBalance', (('0x' + '00' * 20), "0x3")), - 2 ), ( { @@ -146,7 +208,6 @@ def test_default_input_munger_with_input_parameters_exception(): ['0x0000000000000000000000000000000000000000', 3], {}, ('eth_getBalance', (('0x' + '00' * 20), "0x3")), - 2 ), ( { @@ -159,7 +220,6 @@ def test_default_input_munger_with_input_parameters_exception(): [1, 2, 3, ('0x' + '00' * 20)], {}, ('eth_getBalance', (('0x' + '00' * 20), "1")), - 2, ), ( { @@ -172,7 +232,6 @@ def test_default_input_munger_with_input_parameters_exception(): [1, 2, 3, 4], {}, TypeError, - 2, ), ( { @@ -182,7 +241,14 @@ def test_default_input_munger_with_input_parameters_exception(): ('0x0000000000000000000000000000000000000000', 3), {}, ('eth_getBalance', ('0x0000000000000000000000000000000000000000', '0x3')), - 2, + ), + ( + { + 'json_rpc_method': 'eth_getBalance', + }, + ('0x0000000000000000000000000000000000000000', 3), + {}, + ('eth_getBalance', ('0x0000000000000000000000000000000000000000', '0x3')), ), ( { @@ -195,7 +261,6 @@ def test_default_input_munger_with_input_parameters_exception(): [('0x' + '00' * 20), 1, 2, 3], {}, ('eth_getBalance', (('0x' + '00' * 20), '1')), - 2, ), ( { @@ -205,19 +270,29 @@ def test_default_input_munger_with_input_parameters_exception(): [], {}, ('eth_chainId', ()), - 2, - ) + ), + ( + { + 'is_property': True, + 'json_rpc_method': 'eth_chainId', + }, + [], + {}, + ('eth_chainId', ()), + ), ), ids=[ 'raises-error-no-rpc-method', - 'test-unexpected-arg', + 'test-missing-argument', 'test-rpc-method-as-string', 'test-rpc-method-as-callable', 'test-arg-munger', - 'test-munger-wrong-length-arg', - 'test-request-formatters', + 'test-munger-too-many-args', + 'test-request-formatters-default-root-munger-explicit', + 'test-request-formatters-default-root-munger-implicit', 'test-mungers-and-request-formatters', 'test-response-formatters', + 'test-set-as-property-default-munger-implicit', ] ) def test_process_params( @@ -225,19 +300,21 @@ def test_process_params( args, kwargs, expected_request_result, - expected_result_formatters_len): +): if isclass(expected_request_result) and issubclass(expected_request_result, Exception): with pytest.raises(expected_request_result): method = Method(**method_config) - request_params, output_formatter = method.process_params(object(), *args, **kwargs) + method.process_params(object(), *args, **kwargs) else: method = Method(**method_config) request_params, output_formatter = method.process_params(object(), *args, **kwargs) assert request_params == expected_request_result first_formatter = (output_formatter[0].first,) all_other_formatters = output_formatter[0].funcs - assert len(first_formatter + all_other_formatters) == expected_result_formatters_len + + # the expected result formatters length is 2 + assert len(first_formatter + all_other_formatters) == 2 def keywords(module, keyword_one, keyword_two): @@ -248,8 +325,8 @@ class Success(Exception): pass -def return_exception_raising_formatter(method): - def formatter(params): +def return_exception_raising_formatter(_method): + def formatter(_params): raise Success() return compose(formatter) diff --git a/tests/core/middleware/test_fixture_middleware.py b/tests/core/middleware/test_fixture_middleware.py index 19ed3816c4..1cb9eb9b7b 100644 --- a/tests/core/middleware/test_fixture_middleware.py +++ b/tests/core/middleware/test_fixture_middleware.py @@ -13,10 +13,7 @@ class DummyProvider(BaseProvider): def make_request(self, method, params): - raise NotImplementedError("Cannot make request for {0}:{1}".format( - method, - params, - )) + raise NotImplementedError(f"Cannot make request for {method}:{params}") @pytest.fixture diff --git a/tests/core/middleware/test_simple_cache_middleware.py b/tests/core/middleware/test_simple_cache_middleware.py index 2f09a10ff9..3cbe96e761 100644 --- a/tests/core/middleware/test_simple_cache_middleware.py +++ b/tests/core/middleware/test_simple_cache_middleware.py @@ -87,7 +87,7 @@ def result_cb(method, params): def test_simple_cache_middleware_does_not_cache_error_responses(w3_base): w3 = w3_base w3.middleware_onion.add(construct_error_generator_middleware({ - 'fake_endpoint': lambda *_: 'msg-{0}'.format(str(uuid.uuid4())), + 'fake_endpoint': lambda *_: f'msg-{uuid.uuid4()}', })) w3.middleware_onion.add(construct_simple_cache_middleware( diff --git a/tests/core/middleware/test_time_based_cache_middleware.py b/tests/core/middleware/test_time_based_cache_middleware.py index 968160171f..d2ee1a9502 100644 --- a/tests/core/middleware/test_time_based_cache_middleware.py +++ b/tests/core/middleware/test_time_based_cache_middleware.py @@ -130,7 +130,7 @@ def test_time_based_cache_middleware_does_not_cache_error_response( counter = itertools.count() def mk_error(method, params): - return "error-number-{0}".format(next(counter)) + return f"error-number-{next(counter)}" w3.middleware_onion.add(construct_error_generator_middleware({ 'fake_endpoint': mk_error, diff --git a/tests/core/middleware/test_transaction_signing.py b/tests/core/middleware/test_transaction_signing.py index bb9d2a7dbb..8d1ccefec8 100644 --- a/tests/core/middleware/test_transaction_signing.py +++ b/tests/core/middleware/test_transaction_signing.py @@ -83,10 +83,7 @@ class DummyProvider(BaseProvider): def make_request(self, method, params): - raise NotImplementedError("Cannot make request for {0}:{1}".format( - method, - params, - )) + raise NotImplementedError(f"Cannot make request for {method}:{params}") @pytest.fixture() diff --git a/tests/core/module-class/test_module.py b/tests/core/module-class/test_module.py new file mode 100644 index 0000000000..ec67fa8d46 --- /dev/null +++ b/tests/core/module-class/test_module.py @@ -0,0 +1,78 @@ +import pytest + +from web3 import ( + EthereumTesterProvider, + Web3, +) +from web3.method import ( + Method, +) + + +@pytest.fixture +def web3_with_external_modules(module1, module2, module3): + return Web3( + EthereumTesterProvider(), + external_modules={ + 'module1': module1, + 'module2': (module2, { + 'submodule1': module3, + }), + } + ) + + +def test_attach_methods_to_module(web3_with_external_modules): + w3 = web3_with_external_modules + + w3.module1.attach_methods({ + # set `property1` on `module1` with `eth_chainId` RPC endpoint + 'property1': Method('eth_chainId', is_property=True), + # set `method1` on `module1` with `eth_getBalance` RPC endpoint + 'method1': Method('eth_getBalance'), + }) + + assert w3.eth.chain_id == 61 + assert w3.module1.property1 == 61 + + coinbase = w3.eth.coinbase + assert w3.eth.get_balance(coinbase, 'latest') == 1000000000000000000000000 + assert w3.module1.method1(coinbase, 'latest') == 1000000000000000000000000 + + w3.module2.submodule1.attach_methods({ + # set `method2` on `module2.submodule1` with `eth_blockNumber` RPC endpoint + 'method2': Method('eth_blockNumber', is_property=True) + }) + + assert w3.eth.block_number == 0 + assert w3.module2.submodule1.method2 == 0 + + w3.eth.attach_methods({'get_block2': Method('eth_getBlockByNumber')}) + + assert w3.eth.get_block('latest')['number'] == 0 + assert w3.eth.get_block('pending')['number'] == 1 + + assert w3.eth.get_block2('latest')['number'] == 0 + assert w3.eth.get_block2('pending')['number'] == 1 + + +def test_attach_methods_with_mungers(web3_with_external_modules): + w3 = web3_with_external_modules + + # `method1` uses `eth_getBlockByNumber` but makes use of unique mungers + w3.module1.attach_methods({ + 'method1': Method('eth_getBlockByNumber', mungers=[ + lambda _method, block_id, full_transactions: (block_id, full_transactions), + # take the user-provided `block_id` and subtract 1 + lambda _method, block_id, full_transactions: (block_id - 1, full_transactions), + ]), + }) + + assert w3.eth.get_block(0, False)['baseFeePerGas'] == 1000000000 + assert w3.eth.get_block(1, False)['baseFeePerGas'] == 875000000 + + # Testing the mungers work: + # `method1` also calls 'eth_getBlockByNumber' but subtracts 1 from the user-provided `block_id` + # due to the second munger. So, `0` from above is a `1` here and `1` is `2`. + assert w3.module1.method1(1, False)['baseFeePerGas'] == 1000000000 + assert w3.module1.method1(2, False)['baseFeePerGas'] == 875000000 diff --git a/tests/core/pm-module/test_ens_integration.py b/tests/core/pm-module/test_ens_integration.py index c6bd38b519..76dc64a1d6 100644 --- a/tests/core/pm-module/test_ens_integration.py +++ b/tests/core/pm-module/test_ens_integration.py @@ -20,7 +20,7 @@ def bytes32(val): if isinstance(val, int): result = to_bytes(val) else: - raise TypeError('val %r could not be converted to bytes') + raise TypeError(f'{val!r} could not be converted to bytes') return result.rjust(32, b'\0') diff --git a/tests/core/providers/test_auto_provider.py b/tests/core/providers/test_auto_provider.py index c0e30295f6..1647d5d502 100644 --- a/tests/core/providers/test_auto_provider.py +++ b/tests/core/providers/test_auto_provider.py @@ -108,7 +108,7 @@ def test_web3_auto_infura(monkeypatch, caplog, environ_name): w3 = infura.w3 assert isinstance(w3.provider, HTTPProvider) - expected_url = 'https://%s/v3/%s' % (infura.INFURA_MAINNET_DOMAIN, API_KEY) + expected_url = f'https://{infura.INFURA_MAINNET_DOMAIN}/v3/{API_KEY}' assert getattr(w3.provider, 'endpoint_uri') == expected_url @@ -117,7 +117,7 @@ def test_web3_auto_infura_websocket_default(monkeypatch, caplog, environ_name): monkeypatch.setenv('WEB3_INFURA_SCHEME', 'wss') API_KEY = 'aoeuhtns' monkeypatch.setenv(environ_name, API_KEY) - expected_url = 'wss://%s/ws/v3/%s' % (infura.INFURA_MAINNET_DOMAIN, API_KEY) + expected_url = f'wss://{infura.INFURA_MAINNET_DOMAIN}/ws/v3/{API_KEY}' importlib.reload(infura) assert len(caplog.record_tuples) == 0 @@ -145,7 +145,7 @@ def test_web3_auto_infura_websocket_with_secret(monkeypatch, caplog, environ_nam w3 = infura.w3 assert isinstance(w3.provider, WebsocketProvider) - expected_url = 'wss://:secret@%s/ws/v3/test' % (infura.INFURA_MAINNET_DOMAIN) + expected_url = f'wss://:secret@{infura.INFURA_MAINNET_DOMAIN}/ws/v3/test' assert getattr(w3.provider, 'endpoint_uri') == expected_url @@ -159,7 +159,7 @@ def test_web3_auto_infura_with_secret(monkeypatch, caplog, environ_name): w3 = infura.w3 assert isinstance(w3.provider, HTTPProvider) - expected_url = 'https://%s/v3/test' % (infura.INFURA_MAINNET_DOMAIN) + expected_url = f'https://{infura.INFURA_MAINNET_DOMAIN}/v3/test' expected_auth_value = ('', 'secret') assert getattr(w3.provider, 'endpoint_uri') == expected_url assert w3.provider.get_request_kwargs()['auth'] == expected_auth_value diff --git a/tests/core/providers/test_ipc_provider.py b/tests/core/providers/test_ipc_provider.py index 2b1042b007..5f856eed7a 100644 --- a/tests/core/providers/test_ipc_provider.py +++ b/tests/core/providers/test_ipc_provider.py @@ -23,7 +23,7 @@ @pytest.fixture def jsonrpc_ipc_pipe_path(): with tempfile.TemporaryDirectory() as temp_dir: - ipc_path = os.path.join(temp_dir, '{0}.ipc'.format(uuid.uuid4())) + ipc_path = os.path.join(temp_dir, f'{uuid.uuid4()}.ipc') try: yield ipc_path finally: diff --git a/tests/core/providers/test_websocket_provider.py b/tests/core/providers/test_websocket_provider.py index 0cf475d31e..25037b1fa0 100644 --- a/tests/core/providers/test_websocket_provider.py +++ b/tests/core/providers/test_websocket_provider.py @@ -55,7 +55,7 @@ async def empty_server(websocket, path): def w3(open_port, start_websocket_server): # need new event loop as the one used by server is already running event_loop = asyncio.new_event_loop() - endpoint_uri = 'ws://127.0.0.1:{}'.format(open_port) + endpoint_uri = f'ws://127.0.0.1:{open_port}' event_loop.run_until_complete(wait_for_ws(endpoint_uri)) provider = WebsocketProvider(endpoint_uri, websocket_timeout=0.01) return Web3(provider) @@ -68,6 +68,6 @@ def test_websocket_provider_timeout(w3): def test_restricted_websocket_kwargs(): invalid_kwargs = {'uri': 'ws://127.0.0.1:8546'} - re_exc_message = r'.*found: {0}*'.format(set(invalid_kwargs.keys())) + re_exc_message = f'.*found: {set(invalid_kwargs)!r}*' with pytest.raises(ValidationError, match=re_exc_message): WebsocketProvider(websocket_kwargs=invalid_kwargs) diff --git a/tests/core/utilities/test_abi_filtering_by_argument_name.py b/tests/core/utilities/test_abi_filtering_by_argument_name.py index 2baac9e355..a9687fb50f 100644 --- a/tests/core/utilities/test_abi_filtering_by_argument_name.py +++ b/tests/core/utilities/test_abi_filtering_by_argument_name.py @@ -48,14 +48,14 @@ @pytest.mark.parametrize( 'argument_names,expected', ( - ([], ['func_1', 'func_2', 'func_3', 'func_4']), - (['a'], ['func_2', 'func_3', 'func_4']), - (['a', 'c'], ['func_4']), - (['c'], ['func_4']), - (['b'], ['func_3', 'func_4']), + ([], {'func_1', 'func_2', 'func_3', 'func_4'}), + (['a'], {'func_2', 'func_3', 'func_4'}), + (['a', 'c'], {'func_4'}), + (['c'], {'func_4'}), + (['b'], {'func_3', 'func_4'}), ) ) def test_filter_by_arguments_1(argument_names, expected): actual_matches = filter_by_argument_name(argument_names, ABI) - function_names = [match['name'] for match in actual_matches] - assert set(function_names) == set(expected) + function_names = {match['name'] for match in actual_matches} + assert function_names == expected diff --git a/tests/core/utilities/test_formatters.py b/tests/core/utilities/test_formatters.py index 649578f7ce..45a56772f8 100644 --- a/tests/core/utilities/test_formatters.py +++ b/tests/core/utilities/test_formatters.py @@ -18,7 +18,7 @@ def square_int(x): return x -@pytest.mark.parametrize('non_collection', [1, 'abc', u'def', True, None]) +@pytest.mark.parametrize('non_collection', [1, 'abc', 'def', True, None]) def test_map_collection_on_non_collection(non_collection): assert map_collection(lambda x: x + 2, non_collection) == non_collection diff --git a/tests/ens/conftest.py b/tests/ens/conftest.py index 7a3d2044dd..a11c5ff7dd 100644 --- a/tests/ens/conftest.py +++ b/tests/ens/conftest.py @@ -34,7 +34,7 @@ def bytes32(val): if isinstance(val, int): result = Web3.toBytes(val) else: - raise TypeError('val %r could not be converted to bytes') + raise TypeError(f'{val!r} could not be converted to bytes') if len(result) < 32: return result.rjust(32, b'\0') else: diff --git a/tests/ethpm/_utils/test_contract_utils.py b/tests/ethpm/_utils/test_contract_utils.py index 1ffd417901..a6bd9f00e2 100644 --- a/tests/ethpm/_utils/test_contract_utils.py +++ b/tests/ethpm/_utils/test_contract_utils.py @@ -58,11 +58,11 @@ def test_validate_contract_name_invalidates(name): @pytest.mark.parametrize( "contract_data,expected_kwargs", ( - ({"abi": ""}, ["abi"]), - ({"deploymentBytecode": {"bytecode": ""}}, ["bytecode"]), + ({"abi": ""}, {"abi"}), + ({"deploymentBytecode": {"bytecode": ""}}, {"bytecode"}), ( {"abi": "", "runtimeBytecode": {"bytecode": ""}}, - ["abi", "bytecode_runtime"], + {"abi", "bytecode_runtime"}, ), ( { @@ -74,13 +74,13 @@ def test_validate_contract_name_invalidates(name): ], }, }, - ["abi", "bytecode", "unlinked_references"], + {"abi", "bytecode", "unlinked_references"}, ), ), ) def test_generate_contract_factory_kwargs(contract_data, expected_kwargs): contract_factory = generate_contract_factory_kwargs(contract_data) - assert set(contract_factory.keys()) == set(expected_kwargs) + assert contract_factory.keys() == expected_kwargs def test_validate_w3_instance_validates(w3): diff --git a/tests/ethpm/validation/test_manifest.py b/tests/ethpm/validation/test_manifest.py index c68089f7cc..6633830f09 100644 --- a/tests/ethpm/validation/test_manifest.py +++ b/tests/ethpm/validation/test_manifest.py @@ -78,11 +78,11 @@ def test_validate_deployments_without_deployment(manifest_with_no_deployments): @pytest.mark.parametrize( "data,expected", ( - ({}, set()), - ([{"some": {"contractType": "one"}}], set(["one"])), + ([], set()), + ([{"some": {"contractType": "one"}}], {"one"}), ( [{"some": {"contractType": "one"}, "other": {"contractType": "two"}}], - set(["one", "two"]), + {"one", "two"}, ), ), ) diff --git a/tests/integration/generate_fixtures/common.py b/tests/integration/generate_fixtures/common.py index d7d20a8964..a6bc487e6b 100644 --- a/tests/integration/generate_fixtures/common.py +++ b/tests/integration/generate_fixtures/common.py @@ -183,11 +183,8 @@ def get_geth_process(geth_binary, output, errors = proc.communicate() print( "Geth Process Exited:\n" - "stdout:{0}\n\n" - "stderr:{1}\n\n".format( - to_text(output), - to_text(errors), - ) + f"stdout:{to_text(output)}\n\n" + f"stderr:{to_text(errors)}\n\n" ) @@ -235,12 +232,13 @@ def mine_transaction_hash(w3, txn_hash): def deploy_contract(w3, name, factory): + name = name.upper() w3.geth.personal.unlock_account(w3.eth.coinbase, KEYFILE_PW) deploy_txn_hash = factory.constructor().transact({'from': w3.eth.coinbase}) - print('{0}_CONTRACT_DEPLOY_HASH: '.format(name.upper()), deploy_txn_hash) + print(f'{name}_CONTRACT_DEPLOY_HASH: {deploy_txn_hash}') deploy_receipt = mine_transaction_hash(w3, deploy_txn_hash) - print('{0}_CONTRACT_DEPLOY_TRANSACTION_MINED'.format(name.upper())) + print(f'{name}_CONTRACT_DEPLOY_TRANSACTION_MINED') contract_address = deploy_receipt['contractAddress'] assert is_checksum_address(contract_address) - print('{0}_CONTRACT_ADDRESS:'.format(name.upper()), contract_address) + print(f'{name}_CONTRACT_ADDRESS: {contract_address}') return deploy_receipt diff --git a/tests/integration/generate_fixtures/go_ethereum.py b/tests/integration/generate_fixtures/go_ethereum.py index d6d55b7ee2..eadbfb00bc 100644 --- a/tests/integration/generate_fixtures/go_ethereum.py +++ b/tests/integration/generate_fixtures/go_ethereum.py @@ -91,11 +91,8 @@ def get_geth_process(geth_binary, print( "Geth Process Exited:\n" - "stdout:{0}\n\n" - "stderr:{1}\n\n".format( - to_text(output), - to_text(errors), - ) + f"stdout:{to_text(output)}\n\n" + f"stderr:{to_text(errors)}\n\n" ) diff --git a/tests/integration/go_ethereum/conftest.py b/tests/integration/go_ethereum/conftest.py index 0862c47aa1..622e11f106 100644 --- a/tests/integration/go_ethereum/conftest.py +++ b/tests/integration/go_ethereum/conftest.py @@ -121,11 +121,8 @@ def geth_process(geth_binary, datadir, genesis_file, geth_command_arguments): output, errors = proc.communicate() print( "Geth Process Exited:\n" - "stdout:{0}\n\n" - "stderr:{1}\n\n".format( - to_text(output), - to_text(errors), - ) + f"stdout:{to_text(output)}\n\n" + f"stderr:{to_text(errors)}\n\n" ) diff --git a/tests/integration/go_ethereum/test_goethereum_http.py b/tests/integration/go_ethereum/test_goethereum_http.py index 53a624faad..ec209d02e1 100644 --- a/tests/integration/go_ethereum/test_goethereum_http.py +++ b/tests/integration/go_ethereum/test_goethereum_http.py @@ -58,7 +58,7 @@ def rpc_port(): @pytest.fixture(scope="module") def endpoint_uri(rpc_port): - return 'http://localhost:{0}'.format(rpc_port) + return f'http://localhost:{rpc_port}' def _geth_command_arguments(rpc_port, diff --git a/tests/integration/go_ethereum/test_goethereum_ws.py b/tests/integration/go_ethereum/test_goethereum_ws.py index 647681ede8..22367aa570 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws.py +++ b/tests/integration/go_ethereum/test_goethereum_ws.py @@ -27,7 +27,7 @@ def ws_port(): @pytest.fixture(scope="module") def endpoint_uri(ws_port): - return 'ws://localhost:{0}'.format(ws_port) + return f'ws://localhost:{ws_port}' def _geth_command_arguments(ws_port, diff --git a/web3/__init__.py b/web3/__init__.py index 5c8edfacef..8d86766529 100644 --- a/web3/__init__.py +++ b/web3/__init__.py @@ -45,7 +45,6 @@ "HTTPProvider", "IPCProvider", "WebsocketProvider", - "TestRPCProvider", "EthereumTesterProvider", "Account", "AsyncHTTPProvider", diff --git a/web3/_utils/abi.py b/web3/_utils/abi.py index 698f4151cd..791f7bdfd5 100644 --- a/web3/_utils/abi.py +++ b/web3/_utils/abi.py @@ -290,9 +290,7 @@ def validate(self) -> None: if self.value_bit_size % 8 != 0: raise ValueError( - "Invalid value bit size: {0}. Must be a multiple of 8".format( - self.value_bit_size, - ) + f"Invalid value bit size: {self.value_bit_size}. Must be a multiple of 8" ) if self.value_bit_size > self.data_byte_size * 8: @@ -328,13 +326,13 @@ def validate_value(self, value: Any) -> bytes: # type: ignore self.invalidate_value( value, exc=ValueOutOfBounds, - msg="exceeds total byte size for bytes{} encoding".format(byte_size), + msg=f"exceeds total byte size for bytes{byte_size} encoding", ) elif len(value) < byte_size: self.invalidate_value( value, exc=ValueOutOfBounds, - msg="less than total byte size for bytes{} encoding".format(byte_size), + msg=f"less than total byte size for bytes{byte_size} encoding", ) return value @@ -438,10 +436,8 @@ def merge_args_and_kwargs( # Ensure the function is being applied to the correct number of args if len(args) + len(kwargs) != len(function_abi.get('inputs', [])): raise TypeError( - "Incorrect argument count. Expected '{0}'. Got '{1}'".format( - len(function_abi['inputs']), - len(args) + len(kwargs), - ) + f"Incorrect argument count. Expected '{len(function_abi['inputs'])}" + f". Got '{len(args) + len(kwargs)}'" ) # If no keyword args were given, we don't need to align them @@ -456,10 +452,8 @@ def merge_args_and_kwargs( duplicate_args = kwarg_names.intersection(args_as_kwargs.keys()) if duplicate_args: raise TypeError( - "{fn_name}() got multiple values for argument(s) '{dups}'".format( - fn_name=function_abi['name'], - dups=', '.join(duplicate_args), - ) + f"{function_abi.get('name')}() got multiple values for argument(s) " + f"'{', '.join(duplicate_args)}'" ) # Check for unknown args @@ -467,16 +461,12 @@ def merge_args_and_kwargs( if unknown_args: if function_abi.get('name'): raise TypeError( - "{fn_name}() got unexpected keyword argument(s) '{dups}'".format( - fn_name=function_abi.get('name'), - dups=', '.join(unknown_args), - ) + f"{function_abi.get('name')}() got unexpected keyword argument(s)" + f" '{', '.join(unknown_args)}'" ) raise TypeError( - "Type: '{_type}' got unexpected keyword argument(s) '{dups}'".format( - _type=function_abi.get('type'), - dups=', '.join(unknown_args), - ) + f"Type: '{function_abi.get('type')}' got unexpected keyword argument(s)" + f" '{', '.join(unknown_args)}'" ) # Sort args according to their position in the ABI and unzip them from their @@ -545,10 +535,8 @@ def _align_abi_input(arg_abi: ABIFunctionParams, arg: Any) -> Tuple[Any, ...]: if not is_list_like(aligned_arg): raise TypeError( - 'Expected non-string sequence for "{}" component type: got {}'.format( - arg_abi['type'], - aligned_arg, - ), + f'Expected non-string sequence for "{arg_abi.get("type")}" ' + f'component type: got {aligned_arg}' ) # convert NamedTuple to regular tuple @@ -604,9 +592,9 @@ def get_constructor_abi(contract_abi: ABI) -> ABIFunction: INT_SIZES = range(8, 257, 8) BYTES_SIZES = range(1, 33) -UINT_TYPES = ['uint{0}'.format(i) for i in INT_SIZES] -INT_TYPES = ['int{0}'.format(i) for i in INT_SIZES] -BYTES_TYPES = ['bytes{0}'.format(i) for i in BYTES_SIZES] + ['bytes32.byte'] +UINT_TYPES = [f'uint{i}' for i in INT_SIZES] +INT_TYPES = [f'int{i}' for i in INT_SIZES] +BYTES_TYPES = [f'bytes{i}' for i in BYTES_SIZES] + ['bytes32.byte'] STATIC_TYPES = list(itertools.chain( ['address', 'bool'], @@ -694,7 +682,7 @@ def size_of_type(abi_type: TypeStr) -> int: def sub_type_of_array_type(abi_type: TypeStr) -> str: if not is_array_type(abi_type): raise ValueError( - "Cannot parse subtype of nonarray abi-type: {0}".format(abi_type) + f"Cannot parse subtype of nonarray abi-type: {abi_type}" ) return re.sub(END_BRACKETS_OF_ARRAY_TYPE_REGEX, '', abi_type, 1) @@ -703,7 +691,7 @@ def sub_type_of_array_type(abi_type: TypeStr) -> str: def length_of_array_type(abi_type: TypeStr) -> int: if not is_array_type(abi_type): raise ValueError( - "Cannot parse length of nonarray abi-type: {0}".format(abi_type) + f"Cannot parse length of nonarray abi-type: {abi_type}" ) inner_brackets = re.search(END_BRACKETS_OF_ARRAY_TYPE_REGEX, abi_type).group(0).strip("[]") diff --git a/web3/_utils/admin.py b/web3/_utils/admin.py index 2505451d96..86e946d550 100644 --- a/web3/_utils/admin.py +++ b/web3/_utils/admin.py @@ -40,19 +40,19 @@ def admin_start_params_munger( datadir: Method[Callable[[], str]] = Method( RPC.admin_datadir, - mungers=None, + is_property=True, ) node_info: Method[Callable[[], NodeInfo]] = Method( RPC.admin_nodeInfo, - mungers=None, + is_property=True, ) peers: Method[Callable[[], List[Peer]]] = Method( RPC.admin_peers, - mungers=None, + is_property=True, ) @@ -77,13 +77,13 @@ def __call__( stop_rpc: Method[Callable[[], bool]] = Method( RPC.admin_stopRPC, - mungers=None, + is_property=True, ) stop_ws: Method[Callable[[], bool]] = Method( RPC.admin_stopWS, - mungers=None, + is_property=True, ) # diff --git a/web3/_utils/async_transactions.py b/web3/_utils/async_transactions.py index 116fdf8898..7e6153e047 100644 --- a/web3/_utils/async_transactions.py +++ b/web3/_utils/async_transactions.py @@ -35,8 +35,8 @@ async def get_buffered_gas_estimate( if gas_estimate > gas_limit: raise ValueError( "Contract does not appear to be deployable within the " - "current network gas limits. Estimated: {0}. Current gas " - "limit: {1}".format(gas_estimate, gas_limit) + f"current network gas limits. Estimated: {gas_estimate}. " + f"Current gas limit: {gas_limit}" ) return min(gas_limit, gas_estimate + gas_buffer) diff --git a/web3/_utils/blocks.py b/web3/_utils/blocks.py index ac875f3c03..dd6c237c76 100644 --- a/web3/_utils/blocks.py +++ b/web3/_utils/blocks.py @@ -31,7 +31,7 @@ def is_predefined_block_number(value: Any) -> bool: elif is_integer(value): return False else: - raise TypeError("unrecognized block reference: %r" % value) + raise TypeError(f"unrecognized block reference: {value!r}") return value_text in {"latest", "pending", "earliest"} @@ -70,5 +70,5 @@ def select_method_for_block_identifier( return if_number else: raise ValueError( - "Value did not match any of the recognized block identifiers: {0}".format(value) + f"Value did not match any of the recognized block identifiers: {value}" ) diff --git a/web3/_utils/caching.py b/web3/_utils/caching.py index 09cdf25335..ee78b48c16 100644 --- a/web3/_utils/caching.py +++ b/web3/_utils/caching.py @@ -39,7 +39,6 @@ def generate_cache_key(value: Any) -> str: in value ))) else: - raise TypeError("Cannot generate cache key for value {0} of type {1}".format( - value, - type(value), - )) + raise TypeError( + f"Cannot generate cache key for value {value} of type {type(value)}" + ) diff --git a/web3/_utils/contracts.py b/web3/_utils/contracts.py index 97a0b1d17c..dcf66f6304 100644 --- a/web3/_utils/contracts.py +++ b/web3/_utils/contracts.py @@ -144,20 +144,11 @@ def find_matching_fn_abi( "\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, + 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) @@ -171,9 +162,7 @@ def encode_abi( if not check_if_arguments_can_be_encoded(abi, w3.codec, arguments, {}): raise TypeError( "One or more arguments could not be encoded to the necessary " - "ABI type. Expected types are: {0}".format( - ', '.join(argument_types), - ) + f"ABI type. Expected types are: {', '.join(argument_types)}" ) normalizers = [ diff --git a/web3/_utils/datatypes.py b/web3/_utils/datatypes.py index 957de2a22e..110449fd75 100644 --- a/web3/_utils/datatypes.py +++ b/web3/_utils/datatypes.py @@ -18,9 +18,9 @@ def verify_attr(class_name: str, key: str, namespace: Collection[str]) -> None: if key not in namespace: raise AttributeError( - "Property {0} not found on {1} class. " - "`{1}.factory` only accepts keyword arguments which are " - "present on the {1} class".format(key, class_name) + f"Property {key} not found on {class_name} class. " + f"`{class_name}.factory` only accepts keyword arguments which are " + f"present on the {class_name} class" ) diff --git a/web3/_utils/decorators.py b/web3/_utils/decorators.py index 63e14d9506..d93ff99277 100644 --- a/web3/_utils/decorators.py +++ b/web3/_utils/decorators.py @@ -24,7 +24,7 @@ def wrapped(*args: Any) -> Any: thread_id = threading.get_ident() thread_local_args = (thread_id,) + arg_instances if thread_local_args in to_wrap.__already_called: # type: ignore - raise ValueError('Recursively called %s with %r' % (to_wrap, args)) + raise ValueError(f'Recursively called {to_wrap} with {args!r}') to_wrap.__already_called[thread_local_args] = True # type: ignore try: wrapped_val = to_wrap(*args) diff --git a/web3/_utils/empty.py b/web3/_utils/empty.py index e6d299b3d6..1fb94fda60 100644 --- a/web3/_utils/empty.py +++ b/web3/_utils/empty.py @@ -7,8 +7,5 @@ class Empty: def __bool__(self) -> Literal[False]: return False - def __nonzero__(self) -> Literal[False]: - return False - empty = Empty() diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py index 085cbf1378..b8adfd88d1 100644 --- a/web3/_utils/encoding.py +++ b/web3/_utils/encoding.py @@ -88,7 +88,7 @@ def hex_encode_abi_type(abi_type: TypeStr, value: Any, return to_hex(text=value) else: raise ValueError( - "Unsupported ABI type: {0}".format(abi_type) + f"Unsupported ABI type: {abi_type}" ) @@ -169,9 +169,7 @@ def hexstr_if_str( (primitive, hexstr) = (None, hexstr_or_primitive) if remove_0x_prefix(HexStr(hexstr)) and not is_hex(hexstr): raise ValueError( - "when sending a str, it must be a hex string. Got: {0!r}".format( - hexstr_or_primitive, - ) + f"when sending a str, it must be a hex string. Got: {hexstr_or_primitive!r}" ) else: (primitive, hexstr) = (hexstr_or_primitive, None) @@ -191,14 +189,14 @@ def _json_mapping_errors(self, mapping: Dict[Any, Any]) -> Iterable[str]: try: self._friendly_json_encode(val) except TypeError as exc: - yield "%r: because (%s)" % (key, exc) + yield f"{key!r}: because ({exc})" def _json_list_errors(self, iterable: Iterable[Any]) -> Iterable[str]: for index, element in enumerate(iterable): try: self._friendly_json_encode(element) except TypeError as exc: - yield "%d: because (%s)" % (index, exc) + yield f"{index}: because ({exc})" def _friendly_json_encode(self, obj: Dict[Any, Any], cls: Optional[Type[json.JSONEncoder]] = None) -> str: @@ -208,10 +206,14 @@ def _friendly_json_encode(self, obj: Dict[Any, Any], except TypeError as full_exception: if hasattr(obj, 'items'): item_errors = '; '.join(self._json_mapping_errors(obj)) - raise TypeError("dict had unencodable value at keys: {{{}}}".format(item_errors)) + raise TypeError( + f"dict had unencodable value at keys: {{{item_errors}}}" + ) elif is_list_like(obj): element_errors = '; '.join(self._json_list_errors(obj)) - raise TypeError("list had unencodable value at index: [{}]".format(element_errors)) + raise TypeError( + f"list had unencodable value at index: [{element_errors}]" + ) else: raise full_exception @@ -220,7 +222,7 @@ def json_decode(self, json_str: str) -> Dict[Any, Any]: decoded = json.loads(json_str) return decoded except json.decoder.JSONDecodeError as exc: - err_msg = 'Could not decode {} because of {}.'.format(repr(json_str), exc) + err_msg = f'Could not decode {json_str!r} because of {exc}.' # Calling code may rely on catching JSONDecodeError to recognize bad json # so we have to re-raise the same type. raise json.decoder.JSONDecodeError(err_msg, exc.doc, exc.pos) @@ -230,7 +232,7 @@ def json_encode(self, obj: Dict[Any, Any], try: return self._friendly_json_encode(obj, cls=cls) except TypeError as exc: - raise TypeError("Could not encode to JSON: {}".format(exc)) + raise TypeError(f"Could not encode to JSON: {exc}") def to_4byte_hex(hex_or_str_or_bytes: Union[HexStr, str, bytes, int]) -> HexStr: @@ -238,7 +240,7 @@ def to_4byte_hex(hex_or_str_or_bytes: Union[HexStr, str, bytes, int]) -> HexStr: byte_str = hexstr_if_str(to_bytes, hex_or_str_or_bytes) if len(byte_str) > 4: raise ValueError( - 'expected value of size 4 bytes. Got: %d bytes' % len(byte_str) + f'expected value of size 4 bytes. Got: {len(byte_str)} bytes' ) hex_str = encode_hex(byte_str) return pad_hex(hex_str, size_of_4bytes) diff --git a/web3/_utils/ens.py b/web3/_utils/ens.py index f924d306d7..c5ba63deb5 100644 --- a/web3/_utils/ens.py +++ b/web3/_utils/ens.py @@ -46,7 +46,7 @@ def validate_name_has_address(ens: ENS, name: str) -> ChecksumAddress: if addr: return addr else: - raise NameNotFound("Could not find address for name %r" % name) + raise NameNotFound(f"Could not find address for name {name!r}") class StaticENS: diff --git a/web3/_utils/events.py b/web3/_utils/events.py index 9cfe6fb685..dbb1bcbf27 100644 --- a/web3/_utils/events.py +++ b/web3/_utils/events.py @@ -219,10 +219,9 @@ def get_event_data(abi_codec: ABICodec, event_abi: ABIEvent, log_entry: LogRecei log_topic_names = get_abi_input_names(ABIEvent({'inputs': log_topics_abi})) if len(log_topics) != len(log_topic_types): - raise LogTopicError("Expected {0} log topics. Got {1}".format( - len(log_topic_types), - len(log_topics), - )) + raise LogTopicError( + f"Expected {len(log_topic_types)} log topics. Got {len(log_topics)}" + ) log_data = hexstr_if_str(to_bytes, log_entry['data']) log_data_abi = exclude_indexed_event_inputs(event_abi) @@ -400,7 +399,7 @@ def filter_params(self) -> FilterParams: def deploy(self, w3: "Web3") -> "LogFilter": if not isinstance(w3, web3.Web3): - raise ValueError("Invalid web3 argument: got: {0}".format(repr(w3))) + raise ValueError(f"Invalid web3 argument: got: {w3!r}") for arg in AttributeDict.values(self.args): arg._immutable = True diff --git a/web3/_utils/filters.py b/web3/_utils/filters.py index b6a77c385f..79d3ae734f 100644 --- a/web3/_utils/filters.py +++ b/web3/_utils/filters.py @@ -97,7 +97,7 @@ def construct_event_filter_params( filter_params['address'] = [address, contract_address] else: raise ValueError( - "Unsupported type for `address` parameter: {0}".format(type(address)) + f"Unsupported type for `address` parameter: {type(address)}" ) elif address: filter_params['address'] = address diff --git a/web3/_utils/http.py b/web3/_utils/http.py index 80c7e31296..e244183877 100644 --- a/web3/_utils/http.py +++ b/web3/_utils/http.py @@ -1,8 +1,5 @@ def construct_user_agent(class_name: str) -> str: from web3 import __version__ as web3_version - user_agent = 'Web3.py/{version}/{class_name}'.format( - version=web3_version, - class_name=class_name, - ) + user_agent = f'Web3.py/{web3_version}/{class_name}' return user_agent diff --git a/web3/_utils/math.py b/web3/_utils/math.py index cb983f235b..96052bb00d 100644 --- a/web3/_utils/math.py +++ b/web3/_utils/math.py @@ -14,9 +14,10 @@ def percentile(values: Optional[Sequence[int]] = None, """ if values in [None, tuple(), []] or len(values) < 1: raise InsufficientData( - "Expected a sequence of at least 1 integers, got {0!r}".format(values)) + f"Expected a sequence of at least 1 integers, got {values!r}" + ) if percentile is None: - raise ValueError("Expected a percentile choice, got {0}".format(percentile)) + raise ValueError(f"Expected a percentile choice, got {percentile}") sorted_values = sorted(values) diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py index 5105b5f0e0..7a1ff17a7b 100644 --- a/web3/_utils/method_formatters.py +++ b/web3/_utils/method_formatters.py @@ -123,7 +123,7 @@ def to_hexbytes( if isinstance(val, (str, int, bytes)): result = HexBytes(val) else: - raise TypeError("Cannot convert %r to HexBytes" % val) + raise TypeError(f"Cannot convert {val!r} to HexBytes") extra_bytes = len(result) - num_bytes if extra_bytes == 0 or (variable_length and extra_bytes < 0): @@ -132,9 +132,7 @@ def to_hexbytes( return HexBytes(result[extra_bytes:]) else: raise ValueError( - "The value %r is %d bytes, but should be %d" % ( - result, len(result), num_bytes - ) + f"The value {result!r} is {len(result)} bytes, but should be {num_bytes}" ) diff --git a/web3/_utils/miner.py b/web3/_utils/miner.py index 873f296d95..f39c99953e 100644 --- a/web3/_utils/miner.py +++ b/web3/_utils/miner.py @@ -51,19 +51,19 @@ stop: Method[Callable[[], bool]] = Method( RPC.miner_stop, - mungers=None, + is_property=True, ) start_auto_dag: Method[Callable[[], bool]] = Method( RPC.miner_startAutoDag, - mungers=None, + is_property=True, ) stop_auto_dag: Method[Callable[[], bool]] = Method( RPC.miner_stopAutoDag, - mungers=None, + is_property=True, ) diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index a2e4b66ea9..f2fa5bee92 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -2739,7 +2739,7 @@ def test_eth_getTransactionByHash_contract_creation( ) -> None: transaction = w3.eth.get_transaction(math_contract_deploy_txn_hash) assert is_dict(transaction) - assert transaction['to'] is None, "to field is %r" % transaction['to'] + assert transaction['to'] is None, f"to field is {transaction['to']!r}" def test_eth_getTransactionByBlockHashAndIndex( self, w3: "Web3", block_with_txn: BlockData, mined_txn_hash: HexStr @@ -3140,7 +3140,7 @@ def test_eth_call_old_contract_state( assert default_call_result == 0 if pending_call_result != 1: - raise AssertionError("pending call result was %d instead of 1" % pending_call_result) + raise AssertionError(f"pending call result was {pending_call_result} instead of 1") def test_eth_uninstallFilter_deprecated(self, w3: "Web3") -> None: filter = w3.eth.filter({}) diff --git a/web3/_utils/module_testing/go_ethereum_admin_module.py b/web3/_utils/module_testing/go_ethereum_admin_module.py index 38505deec7..0fa20285ff 100644 --- a/web3/_utils/module_testing/go_ethereum_admin_module.py +++ b/web3/_utils/module_testing/go_ethereum_admin_module.py @@ -98,7 +98,7 @@ def test_admin_nodeInfo(self, w3: "Web3") -> None: 'protocols': AttributeDict({}) }) # Test that result gives at least the keys that are listed in `expected` - assert not set(expected.keys()).difference(result.keys()) + assert not set(expected).difference(result) class GoEthereumAsyncAdminModuleTest: diff --git a/web3/_utils/normalizers.py b/web3/_utils/normalizers.py index 23667e13e3..13fda343b3 100644 --- a/web3/_utils/normalizers.py +++ b/web3/_utils/normalizers.py @@ -146,9 +146,8 @@ def abi_bytes_to_hex( num_bytes = abi_type.sub if len(bytes_data) > num_bytes: raise ValueError( - "This value was expected to be at most %d bytes, but instead was %d: %r" % ( - (num_bytes, len(bytes_data), data) - ) + f"This value was expected to be at most {num_bytes} bytes, " + f"but instead was {len(bytes_data)}: {data!r}" ) padded = bytes_data.ljust(num_bytes, b'\0') @@ -204,18 +203,18 @@ def abi_ens_resolver(w3: "Web3", type_str: TypeStr, val: Any) -> Tuple[TypeStr, if type_str == 'address' and is_ens_name(val): if w3 is None: raise InvalidAddress( - "Could not look up name %r because no web3" - " connection available" % (val) + f"Could not look up name {val!r} because no web3" + " connection available" ) elif w3.ens is None: raise InvalidAddress( - "Could not look up name %r because ENS is" - " set to None" % (val) + f"Could not look up name {val!r} because ENS is" + " set to None" ) elif int(w3.net.version) != 1 and not isinstance(w3.ens, StaticENS): raise InvalidAddress( - "Could not look up name %r because web3 is" - " not connected to mainnet" % (val) + f"Could not look up name {val!r} because web3 is" + " not connected to mainnet" ) else: return type_str, validate_name_has_address(w3.ens, val) diff --git a/web3/_utils/personal.py b/web3/_utils/personal.py index 2bb2eda77d..d9690cc44a 100644 --- a/web3/_utils/personal.py +++ b/web3/_utils/personal.py @@ -44,13 +44,13 @@ list_accounts: Method[Callable[[], List[ChecksumAddress]]] = Method( RPC.personal_listAccounts, - mungers=None, + is_property=True, ) list_wallets: Method[Callable[[], List[GethWallet]]] = Method( RPC.personal_listWallets, - mungers=None, + is_property=True, ) diff --git a/web3/_utils/rpc_abi.py b/web3/_utils/rpc_abi.py index 87b29e41bb..4ebf435919 100644 --- a/web3/_utils/rpc_abi.py +++ b/web3/_utils/rpc_abi.py @@ -219,7 +219,7 @@ def apply_abi_formatters_to_dict( abi_dict: Dict[str, Any], data: Dict[Any, Any] ) -> Dict[Any, Any]: - fields = list(set(abi_dict.keys()) & set(data.keys())) + fields = list(abi_dict.keys() & data.keys()) formatted_values = map_abi_data( normalizers, [abi_dict[field] for field in fields], @@ -241,4 +241,4 @@ def abi_request_formatters( single_dict_formatter = apply_abi_formatters_to_dict(normalizers, abi_types) yield method, apply_formatter_at_index(single_dict_formatter, 0) else: - raise TypeError("ABI definitions must be a list or dictionary, got %r" % abi_types) + raise TypeError(f"ABI definitions must be a list or dictionary, got {abi_types!r}") diff --git a/web3/_utils/threads.py b/web3/_utils/threads.py index ba45d8775e..435807dbbe 100644 --- a/web3/_utils/threads.py +++ b/web3/_utils/threads.py @@ -49,7 +49,7 @@ def __exit__( def __str__(self) -> str: if self.seconds is None: return '' - return "{0} seconds".format(self.seconds) + return f"{self.seconds} seconds" @property def expire_at(self) -> int: diff --git a/web3/_utils/transactions.py b/web3/_utils/transactions.py index 8d9ca470df..40acdf0780 100644 --- a/web3/_utils/transactions.py +++ b/web3/_utils/transactions.py @@ -109,7 +109,7 @@ def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams: if callable(default_getter): if w3 is None: - raise ValueError("You must specify a '%s' value in the transaction" % key) + raise ValueError(f"You must specify a '{key}' value in the transaction") default_val = default_getter(w3, transaction) else: default_val = default_getter @@ -137,8 +137,8 @@ def get_buffered_gas_estimate( if gas_estimate > gas_limit: raise ValueError( "Contract does not appear to be deployable within the " - "current network gas limits. Estimated: {0}. Current gas " - "limit: {1}".format(gas_estimate, gas_limit) + f"current network gas limits. Estimated: {gas_estimate}. " + f"Current gas limit: {gas_limit}" ) return min(gas_limit, gas_estimate + gas_buffer) @@ -189,7 +189,7 @@ def extract_valid_transaction_params(transaction_params: TxData) -> TxParams: def assert_valid_transaction_params(transaction_params: TxParams) -> None: for param in transaction_params: if param not in VALID_TRANSACTION_PARAMS: - raise ValueError('{} is not a valid transaction parameter'.format(param)) + raise ValueError(f'{param} is not a valid transaction parameter') def prepare_replacement_transaction( diff --git a/web3/_utils/txpool.py b/web3/_utils/txpool.py index 052f8b35ca..7af244b52b 100644 --- a/web3/_utils/txpool.py +++ b/web3/_utils/txpool.py @@ -16,17 +16,17 @@ content: Method[Callable[[], TxPoolContent]] = Method( RPC.txpool_content, - mungers=None, + is_property=True, ) inspect: Method[Callable[[], TxPoolInspect]] = Method( RPC.txpool_inspect, - mungers=None, + is_property=True, ) status: Method[Callable[[], TxPoolStatus]] = Method( RPC.txpool_status, - mungers=None, + is_property=True, ) diff --git a/web3/_utils/validation.py b/web3/_utils/validation.py index 65fcb9f1c0..d6080e340c 100644 --- a/web3/_utils/validation.py +++ b/web3/_utils/validation.py @@ -87,7 +87,7 @@ def validate_abi(abi: ABI) -> None: if duplicates: raise ValueError( 'Abi contains functions with colliding selectors. ' - 'Functions {0}'.format(_prepare_selector_collision_msg(duplicates)) + f'Functions {_prepare_selector_collision_msg(duplicates)}' ) @@ -96,7 +96,7 @@ def validate_abi_type(abi_type: TypeStr) -> None: Helper function for validating an abi_type """ if not is_recognized_type(abi_type): - raise ValueError("Unrecognized abi_type: {abi_type}".format(abi_type=abi_type)) + raise ValueError(f"Unrecognized abi_type: {abi_type}") def validate_abi_value(abi_type: TypeStr, value: Any) -> None: @@ -110,15 +110,13 @@ def validate_abi_value(abi_type: TypeStr, value: Any) -> None: if specified_length is not None: if specified_length < 1: raise TypeError( - "Invalid abi-type: {abi_type}. Length of fixed sized arrays" - "must be greater than 0." - .format(abi_type=abi_type) + f"Invalid abi-type: {abi_type}. Length of fixed sized " + "arrays must be greater than 0." ) if specified_length != len(value): raise TypeError( "The following array length does not the length specified" - "by the abi-type, {abi_type}: {value}" - .format(abi_type=abi_type, value=value) + f"by the abi-type, {abi_type}: {value}" ) # validate sub_types @@ -150,8 +148,7 @@ def validate_abi_value(abi_type: TypeStr, value: Any) -> None: return raise TypeError( - "The following abi value is not a '{abi_type}': {value}" - .format(abi_type=abi_type, value=value) + f"The following abi value is not a '{abi_type}': {value}" ) @@ -174,7 +171,7 @@ def validate_address(value: Any) -> None: return if not isinstance(value, str): - raise TypeError('Address {} must be provided as a string'.format(value)) + raise TypeError(f'Address {value} must be provided as a string') if not is_hex_address(value): raise InvalidAddress("Address must be 20 bytes, as a hex string with a 0x prefix", value) if not is_checksum_address(value): @@ -205,5 +202,5 @@ def assert_one_val(*args: Any, **kwargs: Any) -> None: if not has_one_val(*args, **kwargs): raise TypeError( "Exactly one of the passed values can be specified. " - "Instead, values were: %r, %r" % (args, kwargs) + f"Instead, values were: {args!r}, {kwargs!r}" ) diff --git a/web3/auto/infura/endpoints.py b/web3/auto/infura/endpoints.py index c8de5c78d2..8531e3c023 100644 --- a/web3/auto/infura/endpoints.py +++ b/web3/auto/infura/endpoints.py @@ -56,10 +56,10 @@ def build_infura_url(domain: str) -> URI: secret = load_secret() if scheme == WEBSOCKET_SCHEME and secret != '': - return URI("%s://:%s@%s/ws/v3/%s" % (scheme, secret, domain, key)) + return URI(f"{scheme}://:{secret}@{domain}/ws/v3/{key}") elif scheme == WEBSOCKET_SCHEME and secret == '': - return URI("%s://%s/ws/v3/%s" % (scheme, domain, key)) + return URI(f"{scheme}://{domain}/ws/v3/{key}") elif scheme == HTTP_SCHEME: - return URI("%s://%s/v3/%s" % (scheme, domain, key)) + return URI(f"{scheme}://{domain}/v3/{key}") else: - raise ValidationError("Cannot connect to Infura with scheme %r" % scheme) + raise ValidationError(f"Cannot connect to Infura with scheme {scheme!r}") diff --git a/web3/contract.py b/web3/contract.py index 317028ca08..d3ee8ff9e2 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -201,7 +201,7 @@ def __getattr__(self, function_name: str) -> "ContractFunction": ) elif function_name not in self.__dict__['_functions']: raise ABIFunctionNotFound( - "The function '{}' was not found in this contract's abi. ".format(function_name), + f"The function '{function_name}' was not found in this contract's abi. ", "Are you sure you provided the correct contract abi?" ) else: @@ -281,7 +281,7 @@ def __getattr__(self, event_name: str) -> Type['ContractEvent']: ) elif event_name not in self.__dict__['_events']: raise ABIEventFunctionNotFound( - "The event '{}' was not found in this contract's abi. ".format(event_name), + f"The event '{event_name}' was not found in this contract's abi. ", "Are you sure you provided the correct contract abi?" ) else: @@ -392,7 +392,7 @@ def get_function_by_signature(self, signature: str if ' ' in signature: raise ValueError( 'Function signature should not contain any spaces. ' - 'Found spaces in input: %s' % signature + f'Found spaces in input: {signature}' ) def callable_check(fn_abi: ABIFunction) -> bool: @@ -849,10 +849,10 @@ def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams def check_forbidden_keys_in_transaction( transaction: TxParams, forbidden_keys: Optional[Collection[str]] = None ) -> None: - keys_found = set(transaction.keys()) & set(forbidden_keys) + keys_found = transaction.keys() & forbidden_keys if keys_found: raise ValueError( - "Cannot set '{}' field(s) in transaction".format(', '.join(keys_found)) + f"Cannot set '{', '.join(keys_found)}' field(s) in transaction" ) @@ -916,9 +916,9 @@ def __prepared_function(self, *args: Any, **kwargs: Any) -> 'ContractFunction': modifier, modifier_dict = kwargs.popitem() if modifier not in self.ALLOWED_MODIFIERS: raise TypeError( - "The only allowed keyword arguments are: %s" % self.ALLOWED_MODIFIERS) + f"The only allowed keyword arguments are: {self.ALLOWED_MODIFIERS}") else: - raise TypeError("Use up to one keyword argument, one of: %s" % self.ALLOWED_MODIFIERS) + raise TypeError(f"Use up to one keyword argument, one of: {self.ALLOWED_MODIFIERS}") return getattr(self._function(*args), modifier)(modifier_dict) @@ -1241,11 +1241,11 @@ def _encode_transaction_data(cls) -> HexStr: def __repr__(self) -> str: if self.abi: - _repr = '' - return '' % self.fn_name + return f'' class ContractFunction(BaseContractFunction): @@ -1785,12 +1785,12 @@ def __getattr__(self, function_name: str) -> Any: "The ABI for this contract contains no function definitions. ", "Are you sure you provided the correct contract ABI?" ) - elif function_name not in set(fn['name'] for fn in self._functions): + elif function_name not in {fn['name'] for fn in self._functions}: functions_available = ', '.join([fn['name'] for fn in self._functions]) raise ABIFunctionNotFound( - "The function '{}' was not found in this contract's ABI. ".format(function_name), + f"The function '{function_name}' was not found in this contract's ABI. ", "Here is a list of all of the function names found: ", - "{}. ".format(functions_available), + f"{functions_available}. ", "Did you mean to call one of those functions?" ) else: @@ -2191,11 +2191,11 @@ def get_function_by_identifier( ) -> Union[ContractFunction, AsyncContractFunction]: if len(fns) > 1: raise ValueError( - 'Found multiple functions with matching {0}. ' - 'Found: {1!r}'.format(identifier, fns) + f'Found multiple functions with matching {identifier}. ' + f'Found: {fns!r}' ) elif len(fns) == 0: raise ValueError( - 'Could not find any function with matching {0}'.format(identifier) + f'Could not find any function with matching {identifier}' ) return fns[0] diff --git a/web3/datastructures.py b/web3/datastructures.py index 79f6cd59ec..27b2e73bc2 100644 --- a/web3/datastructures.py +++ b/web3/datastructures.py @@ -57,7 +57,7 @@ def __len__(self) -> int: return len(self.__dict__) def __repr__(self) -> str: - return self.__class__.__name__ + "(%r)" % self.__dict__ + return self.__class__.__name__ + f"({self.__dict__!r})" def _repr_pretty_(self, builder: Any, cycle: bool) -> None: """ @@ -158,12 +158,8 @@ def inject(self, element: TValue, name: Optional[TKey] = None, raise TypeError("The layer for insertion must be an int.") elif layer != 0 and layer != len(self._queue): raise NotImplementedError( - "You can only insert to the beginning or end of a %s, currently. " - "You tried to insert to %d, but only 0 and %d are permitted. " % ( - type(self), - layer, - len(self._queue), - ) + f"You can only insert to the beginning or end of a {type(self)}, currently. " + f"You tried to insert to {layer}, but only 0 and {len(self._queue)} are permitted. " ) self.add(element, name=name) diff --git a/web3/eth.py b/web3/eth.py index 65829228c5..91af703ef9 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -123,7 +123,7 @@ class BaseEth(Module): _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, - mungers=None, + is_property=True, ) @property @@ -248,7 +248,7 @@ def estimate_gas_munger( _max_priority_fee: Method[Callable[..., Wei]] = Method( RPC.eth_maxPriorityFeePerGas, - mungers=None, + is_property=True, ) def get_block_munger( @@ -271,12 +271,12 @@ def get_block_munger( get_block_number: Method[Callable[[], BlockNumber]] = Method( RPC.eth_blockNumber, - mungers=None, + is_property=True, ) get_coinbase: Method[Callable[[], ChecksumAddress]] = Method( RPC.eth_coinbase, - mungers=None, + is_property=True, ) def block_id_munger( @@ -319,27 +319,27 @@ def call_munger( _get_accounts: Method[Callable[[], Tuple[ChecksumAddress]]] = Method( RPC.eth_accounts, - mungers=None, + is_property=True, ) _get_hashrate: Method[Callable[[], int]] = Method( RPC.eth_hashrate, - mungers=None, + is_property=True, ) _chain_id: Method[Callable[[], int]] = Method( RPC.eth_chainId, - mungers=None, + is_property=True, ) _is_mining: Method[Callable[[], bool]] = Method( RPC.eth_mining, - mungers=None, + is_property=True, ) _is_syncing: Method[Callable[[], Union[SyncStatus, bool]]] = Method( RPC.eth_syncing, - mungers=None, + is_property=True, ) _get_transaction_receipt: Method[Callable[[_Hash32], TxReceipt]] = Method( @@ -386,7 +386,14 @@ async def block_number(self) -> BlockNumber: @property async def chain_id(self) -> int: - return await self._chain_id() # type: ignore + if self._default_chain_id is None: + return await self._chain_id() # type: ignore + else: + return self._default_chain_id + + @chain_id.setter + def chain_id(self, value: int) -> None: + self._default_chain_id = value @property async def coinbase(self) -> ChecksumAddress: @@ -592,7 +599,7 @@ def icapNamereg(self) -> NoReturn: _protocol_version: Method[Callable[[], str]] = Method( RPC.eth_protocolVersion, - mungers=None, + is_property=True, ) @property @@ -987,7 +994,7 @@ def getCompilers(self) -> NoReturn: get_work: Method[Callable[[], List[HexBytes]]] = Method( RPC.eth_getWork, - mungers=None, + is_property=True, ) @deprecated_for("generate_gas_price") diff --git a/web3/exceptions.py b/web3/exceptions.py index 3390275151..b7d88d1b59 100644 --- a/web3/exceptions.py +++ b/web3/exceptions.py @@ -52,9 +52,11 @@ class StaleBlockchain(Exception): def __init__(self, block: BlockData, allowable_delay: int) -> None: last_block_date = datetime.datetime.fromtimestamp(block["timestamp"]).strftime('%c') message = ( - "The latest block, #%d, is %d seconds old, but is only allowed to be %d s old. " - "The date of the most recent block is %s. Continue syncing and try again..." % - (block["number"], time.time() - block["timestamp"], allowable_delay, last_block_date) + f"The latest block, #{block['number']}, is " + f"{time.time() - block['timestamp']} seconds old, but is only " + f"allowed to be {allowable_delay} s old. " + f"The date of the most recent block is {last_block_date}. Continue " + "syncing and try again..." ) super().__init__(message, block, allowable_delay) diff --git a/web3/main.py b/web3/main.py index acbac1b9a3..c8cb108922 100644 --- a/web3/main.py +++ b/web3/main.py @@ -292,12 +292,10 @@ def keccak(primitive: Optional[Primitives] = None, text: Optional[str] = None, return eth_utils_keccak(input_bytes) raise TypeError( - "You called keccak with first arg %r and keywords %r. You must call it with one of " - "these approaches: keccak(text='txt'), keccak(hexstr='0x747874'), " - "keccak(b'\\x74\\x78\\x74'), or keccak(0x747874)." % ( - primitive, - {'text': text, 'hexstr': hexstr} - ) + f"You called keccak with first arg {primitive!r} and keywords {{'text': {text!r}, " + f"'hexstr': {hexstr!r}}}. You must call it with one of these approaches: " + "keccak(text='txt'), keccak(hexstr='0x747874'), keccak(b'\\x74\\x78\\x74'), " + "or keccak(0x747874)." ) @combomethod @@ -315,7 +313,7 @@ def solidityKeccak(cls, abi_types: List[TypeStr], values: List[Any]) -> bytes: if len(abi_types) != len(values): raise ValueError( "Length mismatch between provided abi types and values. Got " - "{0} types and {1} values.".format(len(abi_types), len(values)) + f"{len(abi_types)} types and {len(values)} values." ) if isinstance(cls, type): diff --git a/web3/manager.py b/web3/manager.py index 1594c4dd6f..6cc2b68e44 100644 --- a/web3/manager.py +++ b/web3/manager.py @@ -146,7 +146,7 @@ def _make_request( request_func = self.provider.request_func( self.w3, self.middleware_onion) - self.logger.debug("Making request. Method: %s", method) + self.logger.debug(f"Making request. Method: {method}") return request_func(method, params) async def _coro_make_request( @@ -156,7 +156,7 @@ async def _coro_make_request( request_func = await self.provider.request_func( # type: ignore self.w3, self.middleware_onion) - self.logger.debug("Making request. Method: %s", method) + self.logger.debug(f"Making request. Method: {method}") return await request_func(method, params) @staticmethod @@ -230,7 +230,7 @@ def receive_blocking(self, request_id: UUID, timeout: Optional[float] = None) -> try: request = self.pending_requests.pop(request_id) except KeyError: - raise KeyError("Request for id:{0} not found".format(request_id)) + raise KeyError(f"Request for id:{request_id} not found") else: response = request.get(timeout=timeout) diff --git a/web3/method.py b/web3/method.py index 4f08807e40..9e0e6b822f 100644 --- a/web3/method.py +++ b/web3/method.py @@ -15,6 +15,9 @@ ) import warnings +from eth_utils import ( + ValidationError, +) from eth_utils.curried import ( to_tuple, ) @@ -61,14 +64,24 @@ def inner(args: Any) -> TReturn: return inner -def default_munger(module: "Module", *args: Any, **kwargs: Any) -> Tuple[()]: - if not args and not kwargs: - return () - else: - raise TypeError("Parameters passed to method without parameter mungers defined.") +def _set_mungers(mungers: Optional[Sequence[Munger]], is_property: bool) -> Sequence[Any]: + if is_property and mungers: + raise ValidationError("Mungers cannot be used with a property.") + + return ( + mungers if mungers + else [default_munger] if is_property + else [default_root_munger] + ) + +def default_munger(_module: "Module", *args: Any, **kwargs: Any) -> Tuple[()]: + if args or kwargs: + raise ValidationError("Parameters cannot be passed to a property.") + return () -def default_root_munger(module: "Module", *args: Any) -> List[Any]: + +def default_root_munger(_module: "Module", *args: Any) -> List[Any]: return [*args] @@ -81,7 +94,7 @@ class Method(Generic[TFunc]): Calls to the Method go through these steps: 1. input munging - includes normalization, parameter checking, early parameter - formatting. Any processing on the input parameters that need to happen before + formatting. Any processing on the input parameters that need to happen before json_rpc method string selection occurs. A note about mungers: The first (root) munger should reflect the desired @@ -115,26 +128,26 @@ def get_balance_root_munger(module, account, block_identifier=None): and the response formatters are applied to the output. """ def __init__( - self, - json_rpc_method: Optional[RPCEndpoint] = None, - mungers: Optional[Sequence[Munger]] = None, - request_formatters: Optional[Callable[..., TReturn]] = None, - result_formatters: Optional[Callable[..., TReturn]] = None, - error_formatters: Optional[Callable[..., TReturn]] = None, - null_result_formatters: Optional[Callable[..., TReturn]] = None, - method_choice_depends_on_args: Optional[Callable[..., RPCEndpoint]] = None, - w3: Optional["Web3"] = None): - + self, + json_rpc_method: Optional[RPCEndpoint] = None, + mungers: Optional[Sequence[Munger]] = None, + request_formatters: Optional[Callable[..., TReturn]] = None, + result_formatters: Optional[Callable[..., TReturn]] = None, + null_result_formatters: Optional[Callable[..., TReturn]] = None, + method_choice_depends_on_args: Optional[Callable[..., RPCEndpoint]] = None, + is_property: bool = False, + ): self.json_rpc_method = json_rpc_method - self.mungers = mungers or [default_munger] + self.mungers = _set_mungers(mungers, is_property) self.request_formatters = request_formatters or get_request_formatters self.result_formatters = result_formatters or get_result_formatters - self.error_formatters = get_error_formatters self.null_result_formatters = null_result_formatters or get_null_result_formatters self.method_choice_depends_on_args = method_choice_depends_on_args + self.is_property = is_property - def __get__(self, obj: Optional["Module"] = None, - obj_type: Optional[Type["Module"]] = None) -> TFunc: + def __get__( + self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None + ) -> TFunc: if obj is None: raise TypeError( "Direct calls to methods are not supported. " @@ -167,8 +180,8 @@ def input_munger( root_munger = next(mungers_iter) munged_inputs = pipe( root_munger(module, *args, **kwargs), - *map(lambda m: _munger_star_apply(functools.partial(m, module)), mungers_iter)) - + *map(lambda m: _munger_star_apply(functools.partial(m, module)), mungers_iter) + ) return munged_inputs def process_params( @@ -194,17 +207,19 @@ def process_params( params = [] method = self.method_selector_fn() - response_formatters = (self.result_formatters(method, module), - self.error_formatters(method), - self.null_result_formatters(method),) - - request = (method, - _apply_request_formatters(params, self.request_formatters(method))) - + response_formatters = ( + self.result_formatters(method, module), + get_error_formatters(method), + self.null_result_formatters(method), + ) + request = ( + method, + _apply_request_formatters(params, self.request_formatters(method)) + ) return request, response_formatters -class DeprecatedMethod(): +class DeprecatedMethod: def __init__(self, method: Method[Callable[..., Any]], old_name: str, new_name: str) -> None: self.method = method self.old_name = old_name diff --git a/web3/middleware/filter.py b/web3/middleware/filter.py index 5b2f82a417..dee73d7be3 100644 --- a/web3/middleware/filter.py +++ b/web3/middleware/filter.py @@ -293,15 +293,15 @@ def get_logs(self) -> List[LogReceipt]: "fromBlock": "from_block" } -NEW_FILTER_METHODS = set([ +NEW_FILTER_METHODS = { "eth_newBlockFilter", "eth_newFilter", -]) +} -FILTER_CHANGES_METHODS = set([ +FILTER_CHANGES_METHODS = { "eth_getFilterChanges", "eth_getFilterLogs", -]) +} class RequestBlocks: diff --git a/web3/middleware/signing.py b/web3/middleware/signing.py index 4ed76a9830..b0f121f243 100644 --- a/web3/middleware/signing.py +++ b/web3/middleware/signing.py @@ -93,7 +93,7 @@ def to_account(val: Any) -> LocalAccount: "key must be one of the types: " "eth_keys.datatype.PrivateKey, eth_account.signers.local.LocalAccount, " "or raw private key as a hex string or byte string. " - "Was of type {0}".format(type(val))) + f"Was of type {type(val)}") @to_account.register(LocalAccount) diff --git a/web3/middleware/stalecheck.py b/web3/middleware/stalecheck.py index 73e8158770..e8d98188ad 100644 --- a/web3/middleware/stalecheck.py +++ b/web3/middleware/stalecheck.py @@ -20,9 +20,9 @@ if TYPE_CHECKING: from web3 import Web3 # noqa: F401 -SKIP_STALECHECK_FOR_METHODS = set([ +SKIP_STALECHECK_FOR_METHODS = { 'eth_getBlockByNumber', -]) +} def _isfresh(block: BlockData, allowable_delay: int) -> bool: diff --git a/web3/middleware/validation.py b/web3/middleware/validation.py index 5fafce175b..23872858ef 100644 --- a/web3/middleware/validation.py +++ b/web3/middleware/validation.py @@ -59,11 +59,8 @@ def _validate_chain_id(web3_chain_id: int, chain_id: int) -> int: return chain_id else: raise ValidationError( - "The transaction declared chain ID %r, " - "but the connected node is on %r" % ( - chain_id, - web3_chain_id, - ) + f"The transaction declared chain ID {chain_id!r}, " + f"but the connected node is on {web3_chain_id!r}" ) @@ -73,13 +70,11 @@ def _check_extradata_length(val: Any) -> Any: result = HexBytes(val) if len(result) > MAX_EXTRADATA_LENGTH: raise ExtraDataLengthError( - "The field extraData is %d bytes, but should be %d. " - "It is quite likely that you are connected to a POA chain. " - "Refer to " + f"The field extraData is {len(result)} bytes, but should be " + f"{MAX_EXTRADATA_LENGTH}. It is quite likely that you are " + "connected to a POA chain. Refer to " "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 - ) + f"for more details. The full extraData is: {result!r}" ) return val diff --git a/web3/module.py b/web3/module.py index 49fdfb9209..de3343e5a8 100644 --- a/web3/module.py +++ b/web3/module.py @@ -3,6 +3,7 @@ Any, Callable, Coroutine, + Dict, TypeVar, Union, ) @@ -91,3 +92,14 @@ def __init__(self, w3: "Web3") -> None: self.retrieve_caller_fn = retrieve_blocking_method_call_fn(w3, self) self.w3 = w3 self.codec: ABICodec = w3.codec + + def attach_methods( + self, + methods: Dict[str, Method[Callable[..., Any]]], + ) -> None: + for method_name, method_class in methods.items(): + klass = ( + method_class.__get__(obj=self)() if method_class.is_property else + method_class.__get__(obj=self) + ) + setattr(self, method_name, klass) diff --git a/web3/parity.py b/web3/parity.py index 84bcaa5c44..4b47a40b21 100644 --- a/web3/parity.py +++ b/web3/parity.py @@ -94,7 +94,7 @@ class Parity(Module): enode: Method[Callable[[], str]] = Method( RPC.parity_enode, - mungers=None, + is_property=True, ) """ property default_block """ @@ -141,7 +141,7 @@ def list_storage_keys_munger( net_peers: Method[Callable[[], ParityNetPeers]] = Method( RPC.parity_netPeers, - mungers=None + is_property=True ) add_reserved_peer: Method[Callable[[EnodeURI], bool]] = Method( @@ -217,7 +217,7 @@ def trace_transactions_munger( mode: Method[Callable[[], ParityMode]] = Method( RPC.parity_mode, - mungers=None + is_property=True ) # Deprecated Methods diff --git a/web3/providers/async_rpc.py b/web3/providers/async_rpc.py index d3229c71de..04f27f3b3e 100644 --- a/web3/providers/async_rpc.py +++ b/web3/providers/async_rpc.py @@ -58,7 +58,7 @@ async def cache_async_session(self, session: ClientSession) -> None: await _cache_async_session(self.endpoint_uri, session) def __str__(self) -> str: - return "RPC connection {0}".format(self.endpoint_uri) + return f"RPC connection {self.endpoint_uri}" @to_dict def get_request_kwargs(self) -> Iterable[Tuple[str, Any]]: @@ -74,8 +74,7 @@ def get_request_headers(self) -> Dict[str, str]: } async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - self.logger.debug("Making request HTTP. URI: %s, Method: %s", - self.endpoint_uri, method) + self.logger.debug(f"Making request HTTP. URI: {self.endpoint_uri}, Method: {method}") request_data = self.encode_rpc_request(method, params) raw_response = await async_make_post_request( self.endpoint_uri, @@ -83,7 +82,6 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: **self.get_request_kwargs() ) response = self.decode_rpc_response(raw_response) - self.logger.debug("Getting response HTTP. URI: %s, " - "Method: %s, Response: %s", - self.endpoint_uri, method, response) + self.logger.debug(f"Getting response HTTP. URI: {self.endpoint_uri}, " + f"Method: {method}, Response: {response}") return response diff --git a/web3/providers/auto.py b/web3/providers/auto.py index 9d351b3aec..362640b588 100644 --- a/web3/providers/auto.py +++ b/web3/providers/auto.py @@ -55,10 +55,7 @@ def load_provider_from_uri( return WebsocketProvider(uri_string) else: raise NotImplementedError( - 'Web3 does not know how to connect to scheme %r in %r' % ( - uri.scheme, - uri_string, - ) + f'Web3 does not know how to connect to scheme {uri.scheme!r} in {uri_string!r}' ) @@ -105,10 +102,8 @@ def _proxy_request(self, method: RPCEndpoint, params: Any, if provider is None: raise CannotHandleRequest( "Could not discover provider while making request: " - "method:{0}\n" - "params:{1}\n".format( - method, - params)) + f"method:{method}\nparams:{params}\n" + ) return provider.make_request(method, params) diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index a33d337ef6..544da5e863 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -102,11 +102,8 @@ def inner(*args: Any, **kwargs: Any) -> TValue: def client_version(eth_tester: "EthereumTester", params: Any) -> str: # TODO: account for the backend that is in use. from eth_tester import __version__ - return "EthereumTester/{version}/{platform}/python{v.major}.{v.minor}.{v.micro}".format( - version=__version__, - v=sys.version_info, - platform=sys.platform, - ) + v = sys.version_info + return f"EthereumTester/{__version__}/{sys.platform}/python{v.major}.{v.minor}.{v.micro}" @curry diff --git a/web3/providers/ipc.py b/web3/providers/ipc.py index 45faf63b61..9bb3903b32 100644 --- a/web3/providers/ipc.py +++ b/web3/providers/ipc.py @@ -52,7 +52,9 @@ def __init__(self, ipc_path: str) -> None: def __enter__(self) -> socket.socket: if not self.ipc_path: - raise FileNotFoundError("cannot connect to IPC socket at path: %r" % self.ipc_path) + raise FileNotFoundError( + f"cannot connect to IPC socket at path: {self.ipc_path!r}" + ) if not self.sock: self.sock = self._open() @@ -150,8 +152,8 @@ def get_default_ipc_path() -> str: # type: ignore else: raise ValueError( - "Unsupported platform '{0}'. Only darwin/linux/win32/freebsd are " - "supported. You must specify the ipc_path".format(sys.platform) + f"Unsupported platform '{sys.platform}'. Only darwin/linux/win32/" + "freebsd are supported. You must specify the ipc_path" ) @@ -199,8 +201,8 @@ def get_dev_ipc_path() -> str: # type: ignore else: raise ValueError( - "Unsupported platform '{0}'. Only darwin/linux/win32/freebsd are " - "supported. You must specify the ipc_path".format(sys.platform) + f"Unsupported platform '{sys.platform}'. Only darwin/linux/win32/" + "freebsd are supported. You must specify the ipc_path" ) @@ -231,8 +233,7 @@ def __str__(self) -> str: return f"<{self.__class__.__name__} {self.ipc_path}>" def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - self.logger.debug("Making request IPC. Path: %s, Method: %s", - self.ipc_path, method) + self.logger.debug(f"Making request IPC. Path: {self.ipc_path}, Method: {method}") request = self.encode_rpc_request(method, params) with self._lock, self._socket as sock: diff --git a/web3/providers/rpc.py b/web3/providers/rpc.py index 0be744f71d..13e3156aa5 100644 --- a/web3/providers/rpc.py +++ b/web3/providers/rpc.py @@ -66,7 +66,7 @@ def __init__( super().__init__() def __str__(self) -> str: - return "RPC connection {0}".format(self.endpoint_uri) + return f"RPC connection {self.endpoint_uri}" @to_dict def get_request_kwargs(self) -> Iterable[Tuple[str, Any]]: @@ -82,8 +82,7 @@ def get_request_headers(self) -> Dict[str, str]: } def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - self.logger.debug("Making request HTTP. URI: %s, Method: %s", - self.endpoint_uri, method) + self.logger.debug(f"Making request HTTP. URI: {self.endpoint_uri}, Method: {method}") request_data = self.encode_rpc_request(method, params) raw_response = make_post_request( self.endpoint_uri, @@ -91,7 +90,6 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: **self.get_request_kwargs() ) response = self.decode_rpc_response(raw_response) - self.logger.debug("Getting response HTTP. URI: %s, " - "Method: %s, Response: %s", - self.endpoint_uri, method, response) + self.logger.debug(f"Getting response HTTP. URI: {self.endpoint_uri}, " + f"Method: {method}, Response: {response}") return response diff --git a/web3/providers/websocket.py b/web3/providers/websocket.py index e999d56d3f..0713fa830c 100644 --- a/web3/providers/websocket.py +++ b/web3/providers/websocket.py @@ -103,13 +103,13 @@ def __init__( if websocket_kwargs is None: websocket_kwargs = {} else: - found_restricted_keys = set(websocket_kwargs.keys()).intersection( + found_restricted_keys = set(websocket_kwargs).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) + f'{RESTRICTED_WEBSOCKET_KWARGS} are not allowed ' + f'in websocket_kwargs, found: {found_restricted_keys}' ) self.conn = PersistentWebSocket( self.endpoint_uri, websocket_kwargs @@ -117,7 +117,7 @@ def __init__( super().__init__() def __str__(self) -> str: - return "WS connection {0}".format(self.endpoint_uri) + return f"WS connection {self.endpoint_uri}" async def coro_make_request(self, request_data: bytes) -> RPCResponse: async with self.conn as conn: @@ -133,8 +133,8 @@ async def coro_make_request(self, request_data: bytes) -> RPCResponse: ) def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: - self.logger.debug("Making request WebSocket. URI: %s, " - "Method: %s", self.endpoint_uri, method) + self.logger.debug(f"Making request WebSocket. URI: {self.endpoint_uri}, " + f"Method: {method}") request_data = self.encode_rpc_request(method, params) future = asyncio.run_coroutine_threadsafe( self.coro_make_request(request_data), diff --git a/web3/tools/benchmark/node.py b/web3/tools/benchmark/node.py index 86fa3416b7..2cc23d0fe6 100644 --- a/web3/tools/benchmark/node.py +++ b/web3/tools/benchmark/node.py @@ -59,7 +59,7 @@ def _rpc_port(self) -> str: return str(port) def _endpoint_uri(self) -> str: - return "http://localhost:{0}".format(self.rpc_port) + return f"http://localhost:{self.rpc_port}" def _geth_binary(self) -> str: if "GETH_BINARY" in os.environ: diff --git a/web3/tools/pytest_ethereum/linker.py b/web3/tools/pytest_ethereum/linker.py index c0804d0136..d7385ea857 100644 --- a/web3/tools/pytest_ethereum/linker.py +++ b/web3/tools/pytest_ethereum/linker.py @@ -82,7 +82,7 @@ def _deploy( manifest = insert_deployment( package, contract_name, deployment_data, latest_block_uri ) - logger.info("%s deployed." % contract_name) + logger.info(f"{contract_name} deployed.") return Package(manifest, package.w3) @@ -107,8 +107,7 @@ def link(contract: ContractName, linked_type: str, package: Package) -> Package: to_hex(linked_factory.bytecode), ) logger.info( - "%s linked to %s at address %s." - % (contract, linked_type, to_checksum_address(deployment_address)) + f"{contract} linked to {linked_type} at address {to_checksum_address(deployment_address)}." ) return Package(manifest, package.w3) @@ -120,5 +119,5 @@ def run_python(callback_fn: Callable[..., None], package: Package) -> Package: the contracts in the package. """ callback_fn(package) - logger.info("%s python function ran." % callback_fn.__name__) + logger.info(f"{callback_fn.__name__} python function ran.") return package From 1dc9a626ce4e6e2167b5f3a5c2aeeb951bd40e75 Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 14 Mar 2022 22:00:41 -0400 Subject: [PATCH 25/39] added two more async functions --- web3/contract.py | 158 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 28 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index d3ee8ff9e2..26ed67e162 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1114,7 +1114,7 @@ def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams return call_transaction - def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + def _transact(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: transact_transaction: TxParams = {} else: @@ -1139,22 +1139,11 @@ def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: raise ValueError( "Please ensure that this contract instance has an address." ) + return transact_transaction - return transact_with_contract_function( - self.address, - self.w3, - self.function_identifier, - transact_transaction, - self.contract_abi, - self.abi, - *self.args, - **self.kwargs - ) - - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: + def _estimate_gas( + self, transaction: Optional[TxParams] = None + ) -> TxParams: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -1181,18 +1170,7 @@ def estimateGas( raise ValueError( "Please ensure that this contract instance has an address." ) - - return estimate_gas_for_function( - self.address, - self.w3, - self.function_identifier, - estimate_gas_transaction, - self.contract_abi, - self.abi, - block_identifier, - *self.args, - **self.kwargs - ) + return estimate_gas_transaction def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: """ @@ -1315,6 +1293,43 @@ def call(self, transaction: Optional[TxParams] = None, def factory(cls, class_name: str, **kwargs: Any) -> 'ContractFunction': return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + setup_transaction = self._transact(transaction) + return transact_with_contract_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + setup_transaction = self._estimate_gas(transaction) + return estimate_gas_for_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + block_identifier, + *self.args, + **self.kwargs + ) + + @deprecated_for("estimate_gas") + def estimateGas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + return self.estimate_gas(transaction, block_identifier) + class AsyncContractFunction(BaseContractFunction): @@ -1385,6 +1400,36 @@ async def call( def factory(cls, class_name: str, **kwargs: Any) -> 'AsyncContractFunction': return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + setup_transaction = self._transact(transaction) + return await async_transact_with_contract_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + async def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + setup_transaction = self._estimate_gas(transaction) + return await async_estimate_gas_for_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + block_identifier, + *self.args, + **self.kwargs + ) + class BaseContractEvent: """Base class for contract events @@ -2091,6 +2136,34 @@ def transact_with_contract_function( return txn_hash +async def async_transact_with_contract_function( + address: ChecksumAddress, + w3: 'Web3', + function_name: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> HexBytes: + """ + Helper function for interacting with a contract function by sending a + transaction. + """ + transact_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_name, + contract_abi=contract_abi, + transaction=transaction, + fn_abi=fn_abi, + fn_args=args, + fn_kwargs=kwargs, + ) + + txn_hash = await w3.eth.send_transaction(transact_transaction) # type: ignore + return txn_hash + + def estimate_gas_for_function( address: ChecksumAddress, w3: 'Web3', @@ -2120,6 +2193,35 @@ def estimate_gas_for_function( return w3.eth.estimate_gas(estimate_transaction, block_identifier) +async def async_estimate_gas_for_function( + address: ChecksumAddress, + w3: 'Web3', + fn_identifier: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + block_identifier: Optional[BlockIdentifier] = None, + *args: Any, + **kwargs: Any) -> int: + """Estimates gas cost a function call would take. + + Don't call this directly, instead use :meth:`Contract.estimateGas` + on your contract instance. + """ + estimate_transaction = prepare_transaction( + address, + w3, + fn_identifier=fn_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return await w3.eth.estimate_gas(estimate_transaction, block_identifier) # type: ignore + + def build_transaction_for_function( address: ChecksumAddress, w3: 'Web3', From e30aa4e6b1b2b45424da650fc864b1b13e912ba3 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 15 Mar 2022 16:52:37 -0400 Subject: [PATCH 26/39] Feature/asyncify contract (#2392) * asyncify contract.transact and contract.estimate_gas --- web3/contract.py | 158 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 28 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index d3ee8ff9e2..26ed67e162 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1114,7 +1114,7 @@ def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams return call_transaction - def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + def _transact(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: transact_transaction: TxParams = {} else: @@ -1139,22 +1139,11 @@ def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: raise ValueError( "Please ensure that this contract instance has an address." ) + return transact_transaction - return transact_with_contract_function( - self.address, - self.w3, - self.function_identifier, - transact_transaction, - self.contract_abi, - self.abi, - *self.args, - **self.kwargs - ) - - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: + def _estimate_gas( + self, transaction: Optional[TxParams] = None + ) -> TxParams: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -1181,18 +1170,7 @@ def estimateGas( raise ValueError( "Please ensure that this contract instance has an address." ) - - return estimate_gas_for_function( - self.address, - self.w3, - self.function_identifier, - estimate_gas_transaction, - self.contract_abi, - self.abi, - block_identifier, - *self.args, - **self.kwargs - ) + return estimate_gas_transaction def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: """ @@ -1315,6 +1293,43 @@ def call(self, transaction: Optional[TxParams] = None, def factory(cls, class_name: str, **kwargs: Any) -> 'ContractFunction': return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + setup_transaction = self._transact(transaction) + return transact_with_contract_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + setup_transaction = self._estimate_gas(transaction) + return estimate_gas_for_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + block_identifier, + *self.args, + **self.kwargs + ) + + @deprecated_for("estimate_gas") + def estimateGas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + return self.estimate_gas(transaction, block_identifier) + class AsyncContractFunction(BaseContractFunction): @@ -1385,6 +1400,36 @@ async def call( def factory(cls, class_name: str, **kwargs: Any) -> 'AsyncContractFunction': return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + setup_transaction = self._transact(transaction) + return await async_transact_with_contract_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + async def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + setup_transaction = self._estimate_gas(transaction) + return await async_estimate_gas_for_function( + self.address, + self.w3, + self.function_identifier, + setup_transaction, + self.contract_abi, + self.abi, + block_identifier, + *self.args, + **self.kwargs + ) + class BaseContractEvent: """Base class for contract events @@ -2091,6 +2136,34 @@ def transact_with_contract_function( return txn_hash +async def async_transact_with_contract_function( + address: ChecksumAddress, + w3: 'Web3', + function_name: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> HexBytes: + """ + Helper function for interacting with a contract function by sending a + transaction. + """ + transact_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_name, + contract_abi=contract_abi, + transaction=transaction, + fn_abi=fn_abi, + fn_args=args, + fn_kwargs=kwargs, + ) + + txn_hash = await w3.eth.send_transaction(transact_transaction) # type: ignore + return txn_hash + + def estimate_gas_for_function( address: ChecksumAddress, w3: 'Web3', @@ -2120,6 +2193,35 @@ def estimate_gas_for_function( return w3.eth.estimate_gas(estimate_transaction, block_identifier) +async def async_estimate_gas_for_function( + address: ChecksumAddress, + w3: 'Web3', + fn_identifier: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + block_identifier: Optional[BlockIdentifier] = None, + *args: Any, + **kwargs: Any) -> int: + """Estimates gas cost a function call would take. + + Don't call this directly, instead use :meth:`Contract.estimateGas` + on your contract instance. + """ + estimate_transaction = prepare_transaction( + address, + w3, + fn_identifier=fn_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return await w3.eth.estimate_gas(estimate_transaction, block_identifier) # type: ignore + + def build_transaction_for_function( address: ChecksumAddress, w3: 'Web3', From fa7d4297671de6517e2ba727aae55c8ec883969b Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 17 Mar 2022 22:23:54 -0400 Subject: [PATCH 27/39] fill_transaction_defaults async for later use in build_transaction_for_function --- tests/core/conftest.py | 18 +++++ tests/core/contracts/conftest.py | 16 ---- .../core/utilities/test_async_transaction.py | 22 ++++++ web3/_utils/async_transactions.py | 79 +++++++++++++++++++ 4 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 tests/core/utilities/test_async_transaction.py diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 6947f8d334..ee8a81b91a 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -1,8 +1,17 @@ import pytest +import pytest_asyncio + +from web3 import Web3 +from web3.eth import ( + AsyncEth, +) from web3.module import ( Module, ) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) # --- inherit from `web3.module.Module` class --- # @@ -97,3 +106,12 @@ def __init__(self, a, b): self.a = a self.b = b return ModuleManyArgs + + +@pytest_asyncio.fixture() +async def async_w3(): + provider = AsyncEthereumTesterProvider() + w3 = Web3(provider, modules={'eth': [AsyncEth]}, + middlewares=provider.middlewares) + w3.eth.default_account = await w3.eth.coinbase + return w3 diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index ab9d5abd12..8fd41bc910 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -10,7 +10,6 @@ ) import pytest_asyncio -from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, CONTRACT_EMITTER_CODE, @@ -54,12 +53,6 @@ from web3.contract import ( AsyncContract, ) -from web3.eth import ( - AsyncEth, -) -from web3.providers.eth_tester.main import ( - AsyncEthereumTesterProvider, -) CONTRACT_NESTED_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; @@ -1064,15 +1057,6 @@ async def async_deploy(async_web3, Contract, apply_func=identity, args=None): return contract -@pytest_asyncio.fixture() -async def async_w3(): - provider = AsyncEthereumTesterProvider() - w3 = Web3(provider, modules={'eth': [AsyncEth]}, - middlewares=provider.middlewares) - w3.eth.default_account = await w3.eth.coinbase - return w3 - - @pytest_asyncio.fixture() def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): contract = AsyncContract.factory(async_w3, diff --git a/tests/core/utilities/test_async_transaction.py b/tests/core/utilities/test_async_transaction.py new file mode 100644 index 0000000000..6287fd8665 --- /dev/null +++ b/tests/core/utilities/test_async_transaction.py @@ -0,0 +1,22 @@ +import pytest + +from web3._utils.async_transactions import ( + fill_transaction_defaults, +) + + +@pytest.mark.asyncio() +async def test_fill_transaction_defaults_for_all_params(async_w3): + default_transaction = await fill_transaction_defaults(async_w3, {}) + + block = await async_w3.eth.get_block('latest') + assert default_transaction == { + 'chainId': await async_w3.eth.chain_id, + 'data': b'', + 'gas': await async_w3.eth.estimate_gas({}), + 'maxFeePerGas': ( + await async_w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) + ), + 'maxPriorityFeePerGas': await async_w3.eth.max_priority_fee, + 'value': 0, + } diff --git a/web3/_utils/async_transactions.py b/web3/_utils/async_transactions.py index 7e6153e047..0bd50479c7 100644 --- a/web3/_utils/async_transactions.py +++ b/web3/_utils/async_transactions.py @@ -1,12 +1,25 @@ from typing import ( TYPE_CHECKING, + Awaitable, Optional, cast, ) +from eth_utils.toolz import ( + curry, + merge, +) + +from web3._utils.utility_methods import ( + any_in_dict, +) +from web3.constants import ( + DYNAMIC_FEE_TXN_PARAMS, +) from web3.types import ( BlockIdentifier, TxParams, + Wei, ) if TYPE_CHECKING: @@ -14,6 +27,37 @@ from web3.eth import AsyncEth # noqa: F401 +async def _estimate_gas(w3: 'Web3', tx: TxParams) -> Awaitable[int]: + return await w3.eth.estimate_gas(tx) # type: ignore + + +async def _gas_price(w3: 'Web3', tx: TxParams) -> Awaitable[Optional[Wei]]: + return await w3.eth.generate_gas_price(tx) or w3.eth.gas_price # type: ignore + + +async def _max_fee_per_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]: + block = await w3.eth.get_block('latest') # type: ignore + return await w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) # type: ignore + + +async def _max_priority_fee_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]: + return await w3.eth.max_priority_fee # type: ignore + + +async def _chain_id(w3: 'Web3', tx: TxParams) -> Awaitable[int]: + return await w3.eth.chain_id # type: ignore + +TRANSACTION_DEFAULTS = { + 'value': 0, + 'data': b'', + 'gas': _estimate_gas, + 'gasPrice': _gas_price, + 'maxFeePerGas': _max_fee_per_gas, + 'maxPriorityFeePerGas': _max_priority_fee_gas, + 'chainId': _chain_id, +} + + async def get_block_gas_limit( web3_eth: "AsyncEth", block_identifier: Optional[BlockIdentifier] = None ) -> int: @@ -40,3 +84,38 @@ async def get_buffered_gas_estimate( ) return min(gas_limit, gas_estimate + gas_buffer) + + +@curry +async def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams: + """ + if w3 is None, fill as much as possible while offline + """ + strategy_based_gas_price = await w3.eth.generate_gas_price(transaction) # type: ignore + is_dynamic_fee_transaction = ( + not strategy_based_gas_price + and ( + 'gasPrice' not in transaction # default to dynamic fee transaction + or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) + ) + ) + + defaults = {} + for key, default_getter in TRANSACTION_DEFAULTS.items(): + if key not in transaction: + if ( + is_dynamic_fee_transaction and key == 'gasPrice' + or not is_dynamic_fee_transaction and key in DYNAMIC_FEE_TXN_PARAMS + ): + # do not set default max fees if legacy txn or gas price if dynamic fee txn + continue + + if callable(default_getter): + if w3 is None: + raise ValueError(f"You must specify a '{key}' value in the transaction") + default_val = await default_getter(w3, transaction) + else: + default_val = default_getter + + defaults[key] = default_val + return merge(defaults, transaction) From fdc6d50b994c10b2139bb578232c53c33df83046 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 18 Mar 2022 21:43:13 -0400 Subject: [PATCH 28/39] build_transaction in ContractFunction --- web3/contract.py | 92 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 26ed67e162..ae0629c00a 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -49,6 +49,10 @@ HexBytes, ) +from web3._utils import ( + async_transactions, + transactions, +) from web3._utils.abi import ( abi_to_signature, check_if_arguments_can_be_encoded, @@ -106,9 +110,6 @@ normalize_address_no_ens, normalize_bytecode, ) -from web3._utils.transactions import ( - fill_transaction_defaults, -) from web3.datastructures import ( AttributeDict, MutableAttributeDict, @@ -868,7 +869,7 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return fill_transaction_defaults(self.w3, built_transaction) + return transactions.fill_transaction_defaults(self.w3, built_transaction) @combomethod @deprecated_for("build_transaction") @@ -892,7 +893,7 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return fill_transaction_defaults(self.w3, built_transaction) + return async_transactions.fill_transaction_defaults(self.w3, built_transaction) class ConciseMethod: @@ -1172,10 +1173,7 @@ def _estimate_gas( ) return estimate_gas_transaction - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - """ - Build the transaction dictionary without sending - """ + def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: built_transaction: TxParams = {} else: @@ -1200,16 +1198,7 @@ def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: "Please ensure that this contract instance has an address." ) - return build_transaction_for_function( - self.address, - self.w3, - self.function_identifier, - built_transaction, - self.contract_abi, - self.abi, - *self.args, - **self.kwargs - ) + return built_transaction @combomethod def _encode_transaction_data(cls) -> HexStr: @@ -1330,6 +1319,24 @@ def estimateGas( ) -> int: return self.estimate_gas(transaction, block_identifier) + def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + + built_transaction = self._build_transaction(transaction) + return build_transaction_for_function( + self.address, + self.w3, + self.function_identifier, + built_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + @deprecated_for("build_transaction") + def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: + return self.build_transaction(transaction) + class AsyncContractFunction(BaseContractFunction): @@ -1430,6 +1437,20 @@ async def estimate_gas( **self.kwargs ) + async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + + built_transaction = self._build_transaction(transaction) + return await async_build_transaction_for_function( + self.address, + self.w3, + self.function_identifier, + built_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + class BaseContractEvent: """Base class for contract events @@ -2247,7 +2268,38 @@ def build_transaction_for_function( fn_kwargs=kwargs, ) - prepared_transaction = fill_transaction_defaults(w3, prepared_transaction) + prepared_transaction = transactions.fill_transaction_defaults(w3, prepared_transaction) + + return prepared_transaction + + +async def async_build_transaction_for_function( + address: ChecksumAddress, + w3: 'Web3', + function_name: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> TxParams: + """Builds a dictionary with the fields required to make the given transaction + + Don't call this directly, instead use :meth:`Contract.buildTransaction` + on your contract instance. + """ + prepared_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_name, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + prepared_transaction = await async_transactions.fill_transaction_defaults( + w3, prepared_transaction) return prepared_transaction From 51248eb74181fdec79b22b1b4060e2ed8f1f082a Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 21 Mar 2022 21:38:28 -0400 Subject: [PATCH 29/39] estimateGas async in Contract and docs --- docs/contracts.rst | 34 ++++++++++++++++++++++++++-------- web3/contract.py | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index cd5cedf513..b2e51ad935 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -247,6 +247,11 @@ Each Contract Factory exposes the following methods. .. py:classmethod:: Contract.constructor(*args, **kwargs).estimateGas(transaction=None, block_identifier=None) :noindex: + .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).estimate_gas` + +.. py:classmethod:: Contract.constructor(*args, **kwargs).estimate_gas(transaction=None, block_identifier=None) + :noindex: + Estimate gas for constructing and deploying the contract. This method behaves the same as the @@ -264,12 +269,17 @@ Each Contract Factory exposes the following methods. .. code-block:: python - >>> token_contract.constructor(web3.eth.coinbase, 12345).estimateGas() + >>> token_contract.constructor(web3.eth.coinbase, 12345).estimate_gas() 12563 .. py:classmethod:: Contract.constructor(*args, **kwargs).buildTransaction(transaction=None) :noindex: + .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).build_transaction` + +.. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None) + :noindex: + Construct the contract deploy transaction bytecode data. If the contract takes constructor parameters they should be provided as @@ -286,7 +296,7 @@ Each Contract Factory exposes the following methods. 'gasPrice': w3.eth.gas_price, 'chainId': None } - >>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).buildTransaction(transaction) + >>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).build_transaction(transaction) >>> web3.eth.send_transaction(contract_data) .. _contract_createFilter: @@ -835,6 +845,10 @@ Methods .. py:method:: ContractFunction.estimateGas(transaction, block_identifier=None) + .. warning:: Deprecated: This method is deprecated in favor of :class:`~estimate_gas` + +.. py:method:: ContractFunction.estimate_gas(transaction, block_identifier=None) + Call a contract function, executing the transaction locally using the ``eth_call`` API. This will not create a new public transaction. @@ -842,7 +856,7 @@ Methods .. code-block:: python - myContract.functions.myMethod(*args, **kwargs).estimateGas(transaction) + myContract.functions.myMethod(*args, **kwargs).estimate_gas(transaction) This method behaves the same as the :py:meth:`ContractFunction.transact` method, with transaction details being passed into the end portion of the @@ -853,7 +867,7 @@ Methods .. code-block:: python - >>> my_contract.functions.multiply7(3).estimateGas() + >>> my_contract.functions.multiply7(3).estimate_gas() 42650 .. note:: @@ -863,13 +877,17 @@ Methods .. py:method:: ContractFunction.buildTransaction(transaction) + .. warning:: Deprecated: This method is deprecated in favor of :class:`~build_transaction` + +.. py:method:: ContractFunction.build_transaction(transaction) + Builds a transaction dictionary based on the contract function call specified. Refer to the following invocation: .. code-block:: python - myContract.functions.myMethod(*args, **kwargs).buildTransaction(transaction) + myContract.functions.myMethod(*args, **kwargs).build_transaction(transaction) This method behaves the same as the :py:meth:`Contract.transact` method, with transaction details being passed into the end portion of the @@ -881,7 +899,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'nonce': 10}) + >>> math_contract.functions.increment(5).build_transaction({'nonce': 10}) You may use :meth:`~web3.eth.Eth.getTransactionCount` to get the current nonce for an account. Therefore a shortcut for producing a transaction dictionary with @@ -889,7 +907,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'nonce': web3.eth.get_transaction_count('0xF5...')}) + >>> math_contract.functions.increment(5).build_transaction({'nonce': web3.eth.get_transaction_count('0xF5...')}) Returns a transaction dictionary. This transaction dictionary can then be sent using :meth:`~web3.eth.Eth.send_transaction`. @@ -899,7 +917,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000}) + >>> math_contract.functions.increment(5).build_transaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000}) { 'to': '0x6Bc272FCFcf89C14cebFC57B8f1543F5137F97dE', 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', diff --git a/web3/contract.py b/web3/contract.py index ae0629c00a..150448683e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -803,10 +803,9 @@ def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr: return data @combomethod - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: + def _estimate_gas( + self, transaction: Optional[TxParams] = None + ) -> TxParams: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -820,9 +819,7 @@ def estimateGas( estimate_gas_transaction['data'] = self.data_in_transaction - return self.w3.eth.estimate_gas( - estimate_gas_transaction, block_identifier=block_identifier - ) + return estimate_gas_transaction def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: @@ -879,6 +876,25 @@ def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: """ return self.build_transaction(transaction) + @combomethod + @deprecated_for("estimate_gas") + def estimateGas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + return self.estimate_gas(transaction, block_identifier) + + @combomethod + def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + transaction = self._estimate_gas(transaction) + + return self.w3.eth.estimate_gas( + transaction, block_identifier=block_identifier + ) + class AsyncContractConstructor(BaseContractConstructor): @@ -895,6 +911,17 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP built_transaction = self._build_transaction(transaction) return async_transactions.fill_transaction_defaults(self.w3, built_transaction) + @combomethod + async def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + transaction = self._estimate_gas(transaction) + + return await self.w3.eth.estimate_gas( + transaction, block_identifier=block_identifier + ) + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} From 814d16b77b176cbf0446cfd8d184dace03028643 Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 21 Mar 2022 21:38:38 -0400 Subject: [PATCH 30/39] lint --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 150448683e..aa4cd268b9 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -890,7 +890,7 @@ def estimate_gas( block_identifier: Optional[BlockIdentifier] = None ) -> int: transaction = self._estimate_gas(transaction) - + return self.w3.eth.estimate_gas( transaction, block_identifier=block_identifier ) @@ -918,7 +918,7 @@ async def estimate_gas( ) -> int: transaction = self._estimate_gas(transaction) - return await self.w3.eth.estimate_gas( + return await self.w3.eth.estimate_gas( # type: ignore transaction, block_identifier=block_identifier ) From 268fe5276de2fd0bb6e02c6a884b0f0038fc63b8 Mon Sep 17 00:00:00 2001 From: DB Date: Mon, 21 Mar 2022 23:05:58 -0400 Subject: [PATCH 31/39] docs --- docs/providers.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/providers.rst b/docs/providers.rst index cb1395ecd7..e2cdc2e5bf 100644 --- a/docs/providers.rst +++ b/docs/providers.rst @@ -468,6 +468,12 @@ Geth - :meth:`web3.geth.txpool.content() ` - :meth:`web3.geth.txpool.status() ` +Contract +^^^^^^^^ +Contract is fully implemented for the Async provider. The only documented exception to this at +the moment is where :class:`ENS` is needed for address lookup. All addresses that are passed to Async +contract should not be :class:`ENS` addresses. + Supported Middleware ^^^^^^^^^^^^^^^^^^^^ - :meth:`Gas Price Strategy ` From 9ed1b7a6cbdedf0f363d0c937da36084cf18945a Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 22 Mar 2022 21:03:17 -0400 Subject: [PATCH 32/39] Feature/asyncify contract (#2394) * fill_transaction_defaults async for later use in build_transaction_for_function * build_transaction in ContractFunction * estimateGas async in Contract and docs --- docs/contracts.rst | 34 +++-- docs/providers.rst | 6 + tests/core/conftest.py | 18 +++ tests/core/contracts/conftest.py | 16 --- .../core/utilities/test_async_transaction.py | 22 +++ web3/_utils/async_transactions.py | 79 +++++++++++ web3/contract.py | 133 ++++++++++++++---- 7 files changed, 257 insertions(+), 51 deletions(-) create mode 100644 tests/core/utilities/test_async_transaction.py diff --git a/docs/contracts.rst b/docs/contracts.rst index cd5cedf513..b2e51ad935 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -247,6 +247,11 @@ Each Contract Factory exposes the following methods. .. py:classmethod:: Contract.constructor(*args, **kwargs).estimateGas(transaction=None, block_identifier=None) :noindex: + .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).estimate_gas` + +.. py:classmethod:: Contract.constructor(*args, **kwargs).estimate_gas(transaction=None, block_identifier=None) + :noindex: + Estimate gas for constructing and deploying the contract. This method behaves the same as the @@ -264,12 +269,17 @@ Each Contract Factory exposes the following methods. .. code-block:: python - >>> token_contract.constructor(web3.eth.coinbase, 12345).estimateGas() + >>> token_contract.constructor(web3.eth.coinbase, 12345).estimate_gas() 12563 .. py:classmethod:: Contract.constructor(*args, **kwargs).buildTransaction(transaction=None) :noindex: + .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).build_transaction` + +.. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None) + :noindex: + Construct the contract deploy transaction bytecode data. If the contract takes constructor parameters they should be provided as @@ -286,7 +296,7 @@ Each Contract Factory exposes the following methods. 'gasPrice': w3.eth.gas_price, 'chainId': None } - >>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).buildTransaction(transaction) + >>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).build_transaction(transaction) >>> web3.eth.send_transaction(contract_data) .. _contract_createFilter: @@ -835,6 +845,10 @@ Methods .. py:method:: ContractFunction.estimateGas(transaction, block_identifier=None) + .. warning:: Deprecated: This method is deprecated in favor of :class:`~estimate_gas` + +.. py:method:: ContractFunction.estimate_gas(transaction, block_identifier=None) + Call a contract function, executing the transaction locally using the ``eth_call`` API. This will not create a new public transaction. @@ -842,7 +856,7 @@ Methods .. code-block:: python - myContract.functions.myMethod(*args, **kwargs).estimateGas(transaction) + myContract.functions.myMethod(*args, **kwargs).estimate_gas(transaction) This method behaves the same as the :py:meth:`ContractFunction.transact` method, with transaction details being passed into the end portion of the @@ -853,7 +867,7 @@ Methods .. code-block:: python - >>> my_contract.functions.multiply7(3).estimateGas() + >>> my_contract.functions.multiply7(3).estimate_gas() 42650 .. note:: @@ -863,13 +877,17 @@ Methods .. py:method:: ContractFunction.buildTransaction(transaction) + .. warning:: Deprecated: This method is deprecated in favor of :class:`~build_transaction` + +.. py:method:: ContractFunction.build_transaction(transaction) + Builds a transaction dictionary based on the contract function call specified. Refer to the following invocation: .. code-block:: python - myContract.functions.myMethod(*args, **kwargs).buildTransaction(transaction) + myContract.functions.myMethod(*args, **kwargs).build_transaction(transaction) This method behaves the same as the :py:meth:`Contract.transact` method, with transaction details being passed into the end portion of the @@ -881,7 +899,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'nonce': 10}) + >>> math_contract.functions.increment(5).build_transaction({'nonce': 10}) You may use :meth:`~web3.eth.Eth.getTransactionCount` to get the current nonce for an account. Therefore a shortcut for producing a transaction dictionary with @@ -889,7 +907,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'nonce': web3.eth.get_transaction_count('0xF5...')}) + >>> math_contract.functions.increment(5).build_transaction({'nonce': web3.eth.get_transaction_count('0xF5...')}) Returns a transaction dictionary. This transaction dictionary can then be sent using :meth:`~web3.eth.Eth.send_transaction`. @@ -899,7 +917,7 @@ Methods .. code-block:: python - >>> math_contract.functions.increment(5).buildTransaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000}) + >>> math_contract.functions.increment(5).build_transaction({'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000}) { 'to': '0x6Bc272FCFcf89C14cebFC57B8f1543F5137F97dE', 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', diff --git a/docs/providers.rst b/docs/providers.rst index cb1395ecd7..e2cdc2e5bf 100644 --- a/docs/providers.rst +++ b/docs/providers.rst @@ -468,6 +468,12 @@ Geth - :meth:`web3.geth.txpool.content() ` - :meth:`web3.geth.txpool.status() ` +Contract +^^^^^^^^ +Contract is fully implemented for the Async provider. The only documented exception to this at +the moment is where :class:`ENS` is needed for address lookup. All addresses that are passed to Async +contract should not be :class:`ENS` addresses. + Supported Middleware ^^^^^^^^^^^^^^^^^^^^ - :meth:`Gas Price Strategy ` diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 6947f8d334..ee8a81b91a 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -1,8 +1,17 @@ import pytest +import pytest_asyncio + +from web3 import Web3 +from web3.eth import ( + AsyncEth, +) from web3.module import ( Module, ) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) # --- inherit from `web3.module.Module` class --- # @@ -97,3 +106,12 @@ def __init__(self, a, b): self.a = a self.b = b return ModuleManyArgs + + +@pytest_asyncio.fixture() +async def async_w3(): + provider = AsyncEthereumTesterProvider() + w3 = Web3(provider, modules={'eth': [AsyncEth]}, + middlewares=provider.middlewares) + w3.eth.default_account = await w3.eth.coinbase + return w3 diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index ab9d5abd12..8fd41bc910 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -10,7 +10,6 @@ ) import pytest_asyncio -from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, CONTRACT_EMITTER_CODE, @@ -54,12 +53,6 @@ from web3.contract import ( AsyncContract, ) -from web3.eth import ( - AsyncEth, -) -from web3.providers.eth_tester.main import ( - AsyncEthereumTesterProvider, -) CONTRACT_NESTED_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; @@ -1064,15 +1057,6 @@ async def async_deploy(async_web3, Contract, apply_func=identity, args=None): return contract -@pytest_asyncio.fixture() -async def async_w3(): - provider = AsyncEthereumTesterProvider() - w3 = Web3(provider, modules={'eth': [AsyncEth]}, - middlewares=provider.middlewares) - w3.eth.default_account = await w3.eth.coinbase - return w3 - - @pytest_asyncio.fixture() def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): contract = AsyncContract.factory(async_w3, diff --git a/tests/core/utilities/test_async_transaction.py b/tests/core/utilities/test_async_transaction.py new file mode 100644 index 0000000000..6287fd8665 --- /dev/null +++ b/tests/core/utilities/test_async_transaction.py @@ -0,0 +1,22 @@ +import pytest + +from web3._utils.async_transactions import ( + fill_transaction_defaults, +) + + +@pytest.mark.asyncio() +async def test_fill_transaction_defaults_for_all_params(async_w3): + default_transaction = await fill_transaction_defaults(async_w3, {}) + + block = await async_w3.eth.get_block('latest') + assert default_transaction == { + 'chainId': await async_w3.eth.chain_id, + 'data': b'', + 'gas': await async_w3.eth.estimate_gas({}), + 'maxFeePerGas': ( + await async_w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) + ), + 'maxPriorityFeePerGas': await async_w3.eth.max_priority_fee, + 'value': 0, + } diff --git a/web3/_utils/async_transactions.py b/web3/_utils/async_transactions.py index 7e6153e047..0bd50479c7 100644 --- a/web3/_utils/async_transactions.py +++ b/web3/_utils/async_transactions.py @@ -1,12 +1,25 @@ from typing import ( TYPE_CHECKING, + Awaitable, Optional, cast, ) +from eth_utils.toolz import ( + curry, + merge, +) + +from web3._utils.utility_methods import ( + any_in_dict, +) +from web3.constants import ( + DYNAMIC_FEE_TXN_PARAMS, +) from web3.types import ( BlockIdentifier, TxParams, + Wei, ) if TYPE_CHECKING: @@ -14,6 +27,37 @@ from web3.eth import AsyncEth # noqa: F401 +async def _estimate_gas(w3: 'Web3', tx: TxParams) -> Awaitable[int]: + return await w3.eth.estimate_gas(tx) # type: ignore + + +async def _gas_price(w3: 'Web3', tx: TxParams) -> Awaitable[Optional[Wei]]: + return await w3.eth.generate_gas_price(tx) or w3.eth.gas_price # type: ignore + + +async def _max_fee_per_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]: + block = await w3.eth.get_block('latest') # type: ignore + return await w3.eth.max_priority_fee + (2 * block['baseFeePerGas']) # type: ignore + + +async def _max_priority_fee_gas(w3: 'Web3', tx: TxParams) -> Awaitable[Wei]: + return await w3.eth.max_priority_fee # type: ignore + + +async def _chain_id(w3: 'Web3', tx: TxParams) -> Awaitable[int]: + return await w3.eth.chain_id # type: ignore + +TRANSACTION_DEFAULTS = { + 'value': 0, + 'data': b'', + 'gas': _estimate_gas, + 'gasPrice': _gas_price, + 'maxFeePerGas': _max_fee_per_gas, + 'maxPriorityFeePerGas': _max_priority_fee_gas, + 'chainId': _chain_id, +} + + async def get_block_gas_limit( web3_eth: "AsyncEth", block_identifier: Optional[BlockIdentifier] = None ) -> int: @@ -40,3 +84,38 @@ async def get_buffered_gas_estimate( ) return min(gas_limit, gas_estimate + gas_buffer) + + +@curry +async def fill_transaction_defaults(w3: "Web3", transaction: TxParams) -> TxParams: + """ + if w3 is None, fill as much as possible while offline + """ + strategy_based_gas_price = await w3.eth.generate_gas_price(transaction) # type: ignore + is_dynamic_fee_transaction = ( + not strategy_based_gas_price + and ( + 'gasPrice' not in transaction # default to dynamic fee transaction + or any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) + ) + ) + + defaults = {} + for key, default_getter in TRANSACTION_DEFAULTS.items(): + if key not in transaction: + if ( + is_dynamic_fee_transaction and key == 'gasPrice' + or not is_dynamic_fee_transaction and key in DYNAMIC_FEE_TXN_PARAMS + ): + # do not set default max fees if legacy txn or gas price if dynamic fee txn + continue + + if callable(default_getter): + if w3 is None: + raise ValueError(f"You must specify a '{key}' value in the transaction") + default_val = await default_getter(w3, transaction) + else: + default_val = default_getter + + defaults[key] = default_val + return merge(defaults, transaction) diff --git a/web3/contract.py b/web3/contract.py index 26ed67e162..aa4cd268b9 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -49,6 +49,10 @@ HexBytes, ) +from web3._utils import ( + async_transactions, + transactions, +) from web3._utils.abi import ( abi_to_signature, check_if_arguments_can_be_encoded, @@ -106,9 +110,6 @@ normalize_address_no_ens, normalize_bytecode, ) -from web3._utils.transactions import ( - fill_transaction_defaults, -) from web3.datastructures import ( AttributeDict, MutableAttributeDict, @@ -802,10 +803,9 @@ def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr: return data @combomethod - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: + def _estimate_gas( + self, transaction: Optional[TxParams] = None + ) -> TxParams: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -819,9 +819,7 @@ def estimateGas( estimate_gas_transaction['data'] = self.data_in_transaction - return self.w3.eth.estimate_gas( - estimate_gas_transaction, block_identifier=block_identifier - ) + return estimate_gas_transaction def _get_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: @@ -868,7 +866,7 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return fill_transaction_defaults(self.w3, built_transaction) + return transactions.fill_transaction_defaults(self.w3, built_transaction) @combomethod @deprecated_for("build_transaction") @@ -878,6 +876,25 @@ def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: """ return self.build_transaction(transaction) + @combomethod + @deprecated_for("estimate_gas") + def estimateGas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + return self.estimate_gas(transaction, block_identifier) + + @combomethod + def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + transaction = self._estimate_gas(transaction) + + return self.w3.eth.estimate_gas( + transaction, block_identifier=block_identifier + ) + class AsyncContractConstructor(BaseContractConstructor): @@ -892,7 +909,18 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return fill_transaction_defaults(self.w3, built_transaction) + return async_transactions.fill_transaction_defaults(self.w3, built_transaction) + + @combomethod + async def estimate_gas( + self, transaction: Optional[TxParams] = None, + block_identifier: Optional[BlockIdentifier] = None + ) -> int: + transaction = self._estimate_gas(transaction) + + return await self.w3.eth.estimate_gas( # type: ignore + transaction, block_identifier=block_identifier + ) class ConciseMethod: @@ -1172,10 +1200,7 @@ def _estimate_gas( ) return estimate_gas_transaction - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - """ - Build the transaction dictionary without sending - """ + def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: if transaction is None: built_transaction: TxParams = {} else: @@ -1200,16 +1225,7 @@ def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: "Please ensure that this contract instance has an address." ) - return build_transaction_for_function( - self.address, - self.w3, - self.function_identifier, - built_transaction, - self.contract_abi, - self.abi, - *self.args, - **self.kwargs - ) + return built_transaction @combomethod def _encode_transaction_data(cls) -> HexStr: @@ -1330,6 +1346,24 @@ def estimateGas( ) -> int: return self.estimate_gas(transaction, block_identifier) + def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + + built_transaction = self._build_transaction(transaction) + return build_transaction_for_function( + self.address, + self.w3, + self.function_identifier, + built_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + + @deprecated_for("build_transaction") + def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: + return self.build_transaction(transaction) + class AsyncContractFunction(BaseContractFunction): @@ -1430,6 +1464,20 @@ async def estimate_gas( **self.kwargs ) + async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: + + built_transaction = self._build_transaction(transaction) + return await async_build_transaction_for_function( + self.address, + self.w3, + self.function_identifier, + built_transaction, + self.contract_abi, + self.abi, + *self.args, + **self.kwargs + ) + class BaseContractEvent: """Base class for contract events @@ -2247,7 +2295,38 @@ def build_transaction_for_function( fn_kwargs=kwargs, ) - prepared_transaction = fill_transaction_defaults(w3, prepared_transaction) + prepared_transaction = transactions.fill_transaction_defaults(w3, prepared_transaction) + + return prepared_transaction + + +async def async_build_transaction_for_function( + address: ChecksumAddress, + w3: 'Web3', + function_name: Optional[FunctionIdentifier] = None, + transaction: Optional[TxParams] = None, + contract_abi: Optional[ABI] = None, + fn_abi: Optional[ABIFunction] = None, + *args: Any, + **kwargs: Any) -> TxParams: + """Builds a dictionary with the fields required to make the given transaction + + Don't call this directly, instead use :meth:`Contract.buildTransaction` + on your contract instance. + """ + prepared_transaction = prepare_transaction( + address, + w3, + fn_identifier=function_name, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + prepared_transaction = await async_transactions.fill_transaction_defaults( + w3, prepared_transaction) return prepared_transaction From e6b352aadbdfee392cf9ce064646df9aa8123420 Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 23 Mar 2022 22:32:42 -0400 Subject: [PATCH 33/39] setting default contract factory in AsyncEth to AsyncContract --- ens/main.py | 4 ++-- ethpm/package.py | 2 +- web3/eth.py | 40 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/ens/main.py b/ens/main.py index 245cc56180..71d26a2d86 100644 --- a/ens/main.py +++ b/ens/main.py @@ -263,7 +263,7 @@ def resolver(self, normal_name: str) -> Optional['Contract']: if is_none_or_zero_address(resolver_addr): return None # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) # type: ignore + return self._resolverContract(address=resolver_addr) def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -394,7 +394,7 @@ def _set_resolver( resolver_addr ).transact(transact) # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) # type: ignore + return self._resolverContract(address=resolver_addr) def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 5eeba64aeb..42a350efa6 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -310,7 +310,7 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac address=address, **contract_kwargs ) # TODO: type ignore may be able to be removed after more of AsynContract is finished - return contract_instance # type: ignore + return contract_instance # # Build Dependencies diff --git a/web3/eth.py b/web3/eth.py index 91af703ef9..c5c273caf9 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -118,8 +118,6 @@ class BaseEth(Module): _default_block: BlockIdentifier = "latest" _default_chain_id: Optional[int] = None gasPriceStrategy = None - defaultContractFactory: Type[Union[Contract, AsyncContract, - ConciseContract, ContractCaller, AsyncContractCaller]] = Contract _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, @@ -374,6 +372,7 @@ def set_contract_factory( class AsyncEth(BaseEth): is_async = True + defaultContractFactory: Type[Union[AsyncContract, AsyncContractCaller]] = AsyncContract @property async def accounts(self) -> Tuple[ChecksumAddress]: @@ -586,10 +585,29 @@ async def call( ) -> Union[bytes, bytearray]: return await self._call(transaction, block_identifier, state_override) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> AsyncContract: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[AsyncContract], AsyncContract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + class Eth(BaseEth): account = Account() iban = Iban + defaultContractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] = Contract def namereg(self) -> NoReturn: raise NotImplementedError() @@ -997,6 +1015,24 @@ def getCompilers(self) -> NoReturn: is_property=True, ) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[Contract], Contract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + @deprecated_for("generate_gas_price") def generateGasPrice(self, transaction_params: Optional[TxParams] = None) -> Optional[Wei]: return self._generate_gas_price(transaction_params) From fb238c91665a46395c4d744d7eee4f3684358407 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Thu, 24 Mar 2022 16:33:05 -0400 Subject: [PATCH 34/39] Feature/asyncify contract (#2404) * setting default contract factory in AsyncEth to AsyncContract --- ens/main.py | 4 ++-- ethpm/package.py | 2 +- web3/eth.py | 40 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/ens/main.py b/ens/main.py index 245cc56180..71d26a2d86 100644 --- a/ens/main.py +++ b/ens/main.py @@ -263,7 +263,7 @@ def resolver(self, normal_name: str) -> Optional['Contract']: if is_none_or_zero_address(resolver_addr): return None # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) # type: ignore + return self._resolverContract(address=resolver_addr) def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -394,7 +394,7 @@ def _set_resolver( resolver_addr ).transact(transact) # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) # type: ignore + return self._resolverContract(address=resolver_addr) def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 5eeba64aeb..42a350efa6 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -310,7 +310,7 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac address=address, **contract_kwargs ) # TODO: type ignore may be able to be removed after more of AsynContract is finished - return contract_instance # type: ignore + return contract_instance # # Build Dependencies diff --git a/web3/eth.py b/web3/eth.py index 91af703ef9..c5c273caf9 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -118,8 +118,6 @@ class BaseEth(Module): _default_block: BlockIdentifier = "latest" _default_chain_id: Optional[int] = None gasPriceStrategy = None - defaultContractFactory: Type[Union[Contract, AsyncContract, - ConciseContract, ContractCaller, AsyncContractCaller]] = Contract _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, @@ -374,6 +372,7 @@ def set_contract_factory( class AsyncEth(BaseEth): is_async = True + defaultContractFactory: Type[Union[AsyncContract, AsyncContractCaller]] = AsyncContract @property async def accounts(self) -> Tuple[ChecksumAddress]: @@ -586,10 +585,29 @@ async def call( ) -> Union[bytes, bytearray]: return await self._call(transaction, block_identifier, state_override) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> AsyncContract: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[AsyncContract], AsyncContract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + class Eth(BaseEth): account = Account() iban = Iban + defaultContractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] = Contract def namereg(self) -> NoReturn: raise NotImplementedError() @@ -997,6 +1015,24 @@ def getCompilers(self) -> NoReturn: is_property=True, ) + @overload + def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 + + @overload # noqa: F811 + def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 + + def contract( # noqa: F811 + self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any + ) -> Union[Type[Contract], Contract]: + ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) + + ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) + + if address: + return ContractFactory(address) + else: + return ContractFactory + @deprecated_for("generate_gas_price") def generateGasPrice(self, transaction_params: Optional[TxParams] = None) -> Optional[Wei]: return self._generate_gas_price(transaction_params) From d1675a19e82447f36d2b9e5f02d3eaa5165a76ff Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 24 Mar 2022 20:18:32 -0400 Subject: [PATCH 35/39] defaultContractFacotry --- ens/main.py | 4 ++-- ethpm/package.py | 2 +- web3/eth.py | 37 +------------------------------------ 3 files changed, 4 insertions(+), 39 deletions(-) diff --git a/ens/main.py b/ens/main.py index 71d26a2d86..245cc56180 100644 --- a/ens/main.py +++ b/ens/main.py @@ -263,7 +263,7 @@ def resolver(self, normal_name: str) -> Optional['Contract']: if is_none_or_zero_address(resolver_addr): return None # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) + return self._resolverContract(address=resolver_addr) # type: ignore def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -394,7 +394,7 @@ def _set_resolver( resolver_addr ).transact(transact) # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) + return self._resolverContract(address=resolver_addr) # type: ignore def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 42a350efa6..5eeba64aeb 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -310,7 +310,7 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac address=address, **contract_kwargs ) # TODO: type ignore may be able to be removed after more of AsynContract is finished - return contract_instance + return contract_instance # type: ignore # # Build Dependencies diff --git a/web3/eth.py b/web3/eth.py index c5c273caf9..c1bf069032 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -118,6 +118,7 @@ class BaseEth(Module): _default_block: BlockIdentifier = "latest" _default_chain_id: Optional[int] = None gasPriceStrategy = None + defaultContractFactory: Any = None _gas_price: Method[Callable[[], Wei]] = Method( RPC.eth_gasPrice, @@ -585,24 +586,6 @@ async def call( ) -> Union[bytes, bytearray]: return await self._call(transaction, block_identifier, state_override) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> AsyncContract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[AsyncContract], AsyncContract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - class Eth(BaseEth): account = Account() @@ -1015,24 +998,6 @@ def getCompilers(self) -> NoReturn: is_property=True, ) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[Contract], Contract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - @deprecated_for("generate_gas_price") def generateGasPrice(self, transaction_params: Optional[TxParams] = None) -> Optional[Wei]: return self._generate_gas_price(transaction_params) From d34b0395264616b87d20cafcf44dacec16cfb568 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 24 Mar 2022 20:59:29 -0400 Subject: [PATCH 36/39] defaultContractFactory --- ens/main.py | 4 ++-- ethpm/package.py | 2 +- web3/eth.py | 36 ------------------------------------ 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/ens/main.py b/ens/main.py index 71d26a2d86..245cc56180 100644 --- a/ens/main.py +++ b/ens/main.py @@ -263,7 +263,7 @@ def resolver(self, normal_name: str) -> Optional['Contract']: if is_none_or_zero_address(resolver_addr): return None # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) + return self._resolverContract(address=resolver_addr) # type: ignore def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: reversed_domain = address_to_reverse_domain(target_address) @@ -394,7 +394,7 @@ def _set_resolver( resolver_addr ).transact(transact) # TODO: look at possibly removing type ignore when AsyncENS is written - return self._resolverContract(address=resolver_addr) + return self._resolverContract(address=resolver_addr) # type: ignore def _setup_reverse( self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None diff --git a/ethpm/package.py b/ethpm/package.py index 42a350efa6..5eeba64aeb 100644 --- a/ethpm/package.py +++ b/ethpm/package.py @@ -310,7 +310,7 @@ def get_contract_instance(self, name: ContractName, address: Address) -> Contrac address=address, **contract_kwargs ) # TODO: type ignore may be able to be removed after more of AsynContract is finished - return contract_instance + return contract_instance # type: ignore # # Build Dependencies diff --git a/web3/eth.py b/web3/eth.py index 181e7dba7f..c1bf069032 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -586,24 +586,6 @@ async def call( ) -> Union[bytes, bytearray]: return await self._call(transaction, block_identifier, state_override) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[AsyncContract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> AsyncContract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[AsyncContract], AsyncContract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - class Eth(BaseEth): account = Account() @@ -1016,24 +998,6 @@ def getCompilers(self) -> NoReturn: is_property=True, ) - @overload - def contract(self, address: None = None, **kwargs: Any) -> Type[Contract]: ... # noqa: E704,E501 - - @overload # noqa: F811 - def contract(self, address: Union[Address, ChecksumAddress, ENS], **kwargs: Any) -> Contract: ... # noqa: E704,E501 - - def contract( # noqa: F811 - self, address: Optional[Union[Address, ChecksumAddress, ENS]] = None, **kwargs: Any - ) -> Union[Type[Contract], Contract]: - ContractFactoryClass = kwargs.pop('ContractFactoryClass', self.defaultContractFactory) - - ContractFactory = ContractFactoryClass.factory(self.w3, **kwargs) - - if address: - return ContractFactory(address) - else: - return ContractFactory - @deprecated_for("generate_gas_price") def generateGasPrice(self, transaction_params: Optional[TxParams] = None) -> Optional[Wei]: return self._generate_gas_price(transaction_params) From 17bbcd0f52854b19e68a9bc3df66ab7081621e47 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 24 Mar 2022 23:13:45 -0400 Subject: [PATCH 37/39] renaming deprecated methods --- tests/core/contracts/conftest.py | 10 ++-- .../test_contract_ambiguous_functions.py | 4 +- .../test_contract_buildTransaction.py | 60 +++++++++---------- .../test_contract_class_construction.py | 2 +- .../contracts/test_contract_constructor.py | 18 +++--- .../contracts/test_contract_estimateGas.py | 46 +++++++------- .../test_contract_method_abi_decoding.py | 4 +- .../test_contract_transact_interface.py | 2 +- 8 files changed, 73 insertions(+), 73 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 8fd41bc910..953b866aec 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -1015,7 +1015,7 @@ def invoke_contract(api_call_desig='call', func_args=[], func_kwargs={}, tx_params={}): - allowable_call_desig = ['call', 'transact', 'estimateGas', 'buildTransaction'] + allowable_call_desig = ['call', 'transact', 'estimate_gas', 'build_transaction'] if api_call_desig not in allowable_call_desig: raise ValueError(f"allowable_invoke_method must be one of: {allowable_call_desig}") @@ -1036,13 +1036,13 @@ def call(request): @pytest.fixture -def estimateGas(request): - return functools.partial(invoke_contract, api_call_desig='estimateGas') +def estimate_gas(request): + return functools.partial(invoke_contract, api_call_desig='estimate_gas') @pytest.fixture -def buildTransaction(request): - return functools.partial(invoke_contract, api_call_desig='buildTransaction') +def build_transaction(request): + return functools.partial(invoke_contract, api_call_desig='build_transaction') async def async_deploy(async_web3, Contract, apply_func=identity, args=None): diff --git a/tests/core/contracts/test_contract_ambiguous_functions.py b/tests/core/contracts/test_contract_ambiguous_functions.py index 8f6c0b5ae2..3eb2baa795 100644 --- a/tests/core/contracts/test_contract_ambiguous_functions.py +++ b/tests/core/contracts/test_contract_ambiguous_functions.py @@ -181,8 +181,8 @@ def test_contract_function_methods(string_contract): get_value_func = string_contract.get_function_by_signature('getValue()') assert isinstance(set_value_func('Hello').transact(), HexBytes) assert get_value_func().call() == 'Hello' - assert isinstance(set_value_func('Hello World').estimateGas(), int) - assert isinstance(set_value_func('Hello World').buildTransaction(), dict) + assert isinstance(set_value_func('Hello World').estimate_gas(), int) + assert isinstance(set_value_func('Hello World').build_transaction(), dict) def test_diff_between_fn_and_fn_called(string_contract): diff --git a/tests/core/contracts/test_contract_buildTransaction.py b/tests/core/contracts/test_contract_buildTransaction.py index 13b6727ed0..3f1b6a3723 100644 --- a/tests/core/contracts/test_contract_buildTransaction.py +++ b/tests/core/contracts/test_contract_buildTransaction.py @@ -45,9 +45,9 @@ def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): def test_build_transaction_not_paying_to_nonpayable_function( w3, payable_tester_contract, - buildTransaction): - txn = buildTransaction(contract=payable_tester_contract, - contract_function='doNoValueCall') + build_transaction): + txn = build_transaction(contract=payable_tester_contract, + contract_function='doNoValueCall') assert dissoc(txn, 'gas') == { 'to': payable_tester_contract.address, 'data': '0xe4cb8f5c', @@ -61,15 +61,15 @@ def test_build_transaction_not_paying_to_nonpayable_function( def test_build_transaction_paying_to_nonpayable_function( w3, payable_tester_contract, - buildTransaction): + build_transaction): with pytest.raises(ValidationError): - buildTransaction(contract=payable_tester_contract, - contract_function='doNoValueCall', - tx_params={'value': 1}) + build_transaction(contract=payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) -def test_build_transaction_with_contract_no_arguments(w3, math_contract, buildTransaction): - txn = buildTransaction(contract=math_contract, contract_function='increment') +def test_build_transaction_with_contract_no_arguments(w3, math_contract, build_transaction): + txn = build_transaction(contract=math_contract, contract_function='increment') assert dissoc(txn, 'gas') == { 'to': math_contract.address, 'data': '0xd09de08a', @@ -81,7 +81,7 @@ def test_build_transaction_with_contract_no_arguments(w3, math_contract, buildTr def test_build_transaction_with_contract_fallback_function(w3, fallback_function_contract): - txn = fallback_function_contract.fallback.buildTransaction() + txn = fallback_function_contract.fallback.build_transaction() assert dissoc(txn, 'gas') == { 'to': fallback_function_contract.address, 'data': '0x', @@ -96,8 +96,8 @@ def test_build_transaction_with_contract_class_method( w3, MathContract, math_contract, - buildTransaction): - txn = buildTransaction( + build_transaction): + txn = build_transaction( contract=MathContract, contract_function='increment', tx_params={'to': math_contract.address}, @@ -115,8 +115,8 @@ def test_build_transaction_with_contract_class_method( def test_build_transaction_with_contract_default_account_is_set( w3, math_contract, - buildTransaction): - txn = buildTransaction(contract=math_contract, contract_function='increment') + build_transaction): + txn = build_transaction(contract=math_contract, contract_function='increment') assert dissoc(txn, 'gas') == { 'to': math_contract.address, 'data': '0xd09de08a', @@ -127,11 +127,11 @@ def test_build_transaction_with_contract_default_account_is_set( } -def test_build_transaction_with_gas_price_strategy_set(w3, math_contract, buildTransaction): +def test_build_transaction_with_gas_price_strategy_set(w3, math_contract, build_transaction): def my_gas_price_strategy(w3, transaction_params): return 5 w3.eth.set_gas_price_strategy(my_gas_price_strategy) - txn = buildTransaction(contract=math_contract, contract_function='increment') + txn = build_transaction(contract=math_contract, contract_function='increment') assert dissoc(txn, 'gas') == { 'to': math_contract.address, 'data': '0xd09de08a', @@ -143,20 +143,20 @@ def my_gas_price_strategy(w3, transaction_params): def test_build_transaction_with_contract_data_supplied_errors(w3, math_contract, - buildTransaction): + build_transaction): with pytest.raises(ValueError): - buildTransaction(contract=math_contract, - contract_function='increment', - tx_params={'data': '0x000'}) + build_transaction(contract=math_contract, + contract_function='increment', + tx_params={'data': '0x000'}) def test_build_transaction_with_contract_to_address_supplied_errors(w3, math_contract, - buildTransaction): + build_transaction): with pytest.raises(ValueError): - buildTransaction(contract=math_contract, - contract_function='increment', - tx_params={'to': '0xb2930B35844a230f00E51431aCAe96Fe543a0347'}) + build_transaction(contract=math_contract, + contract_function='increment', + tx_params={'to': '0xb2930B35844a230f00E51431aCAe96Fe543a0347'}) @pytest.mark.parametrize( @@ -219,15 +219,15 @@ def test_build_transaction_with_contract_with_arguments(w3, skip_if_testrpc, mat method_kwargs, expected, skip_testrpc, - buildTransaction): + build_transaction): if skip_testrpc: skip_if_testrpc(w3) - txn = buildTransaction(contract=math_contract, - contract_function='increment', - func_args=method_args, - func_kwargs=method_kwargs, - tx_params=transaction_args) + txn = build_transaction(contract=math_contract, + contract_function='increment', + func_args=method_args, + func_kwargs=method_kwargs, + tx_params=transaction_args) expected['to'] = math_contract.address assert txn is not None if 'gas' in transaction_args: diff --git a/tests/core/contracts/test_contract_class_construction.py b/tests/core/contracts/test_contract_class_construction.py index 1eea5d246a..eb2af91c17 100644 --- a/tests/core/contracts/test_contract_class_construction.py +++ b/tests/core/contracts/test_contract_class_construction.py @@ -53,4 +53,4 @@ def test_error_to_call_non_existent_fallback(w3, bytecode_runtime=MATH_RUNTIME, ) with pytest.raises(FallbackNotFound): - math_contract.fallback.estimateGas() + math_contract.fallback.estimate_gas() diff --git a/tests/core/contracts/test_contract_constructor.py b/tests/core/contracts/test_contract_constructor.py index 36ae246de8..7321ae7ace 100644 --- a/tests/core/contracts/test_contract_constructor.py +++ b/tests/core/contracts/test_contract_constructor.py @@ -16,7 +16,7 @@ def test_contract_constructor_abi_encoding_with_no_constructor_fn(MathContract, def test_contract_constructor_gas_estimate_no_constructor(w3, MathContract): - gas_estimate = MathContract.constructor().estimateGas() + gas_estimate = MathContract.constructor().estimate_gas() deploy_txn = MathContract.constructor().transact() txn_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) @@ -27,7 +27,7 @@ def test_contract_constructor_gas_estimate_no_constructor(w3, MathContract): def test_contract_constructor_gas_estimate_with_block_id(w3, MathContract): block_identifier = None - gas_estimate = MathContract.constructor().estimateGas(block_identifier=block_identifier) + gas_estimate = MathContract.constructor().estimate_gas(block_identifier=block_identifier) deploy_txn = MathContract.constructor().transact() txn_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) gas_used = txn_receipt.get('gasUsed') @@ -38,7 +38,7 @@ def test_contract_constructor_gas_estimate_with_block_id(w3, MathContract): def test_contract_constructor_gas_estimate_with_constructor_without_arguments( w3, SimpleConstructorContract): - gas_estimate = SimpleConstructorContract.constructor().estimateGas() + gas_estimate = SimpleConstructorContract.constructor().estimate_gas() deploy_txn = SimpleConstructorContract.constructor().transact() txn_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) @@ -62,7 +62,7 @@ def test_contract_constructor_gas_estimate_with_constructor_with_arguments( constructor_args, constructor_kwargs): gas_estimate = WithConstructorArgumentsContract.constructor( - *constructor_args, **constructor_kwargs).estimateGas() + *constructor_args, **constructor_kwargs).estimate_gas() deploy_txn = WithConstructorArgumentsContract.constructor( *constructor_args, **constructor_kwargs).transact() @@ -77,7 +77,7 @@ def test_contract_constructor_gas_estimate_with_constructor_with_address_argumen WithConstructorAddressArgumentsContract, address_conversion_func): gas_estimate = WithConstructorAddressArgumentsContract.constructor( - address_conversion_func("0x16D9983245De15E7A9A73bC586E01FF6E08dE737")).estimateGas() + address_conversion_func("0x16D9983245De15E7A9A73bC586E01FF6E08dE737")).estimate_gas() deploy_txn = WithConstructorAddressArgumentsContract.constructor( address_conversion_func("0x16D9983245De15E7A9A73bC586E01FF6E08dE737")).transact() @@ -174,7 +174,7 @@ def test_contract_constructor_transact_with_constructor_with_address_arguments( def test_contract_constructor_build_transaction_to_field_error(MathContract): with pytest.raises(ValueError): - MathContract.constructor().buildTransaction({'to': '123'}) + MathContract.constructor().build_transaction({'to': '123'}) def test_contract_constructor_build_transaction_no_constructor( @@ -186,7 +186,7 @@ def test_contract_constructor_build_transaction_no_constructor( ) txn = w3.eth.get_transaction(txn_hash) nonce = w3.eth.get_transaction_count(w3.eth.coinbase) - unsent_txn = MathContract.constructor().buildTransaction({'nonce': nonce}) + unsent_txn = MathContract.constructor().build_transaction({'nonce': nonce}) assert txn['data'] == unsent_txn['data'] new_txn_hash = w3.eth.send_transaction(unsent_txn) @@ -204,7 +204,7 @@ def test_contract_constructor_build_transaction_with_constructor_without_argumen ) txn = w3.eth.get_transaction(txn_hash) nonce = w3.eth.get_transaction_count(w3.eth.coinbase) - unsent_txn = MathContract.constructor().buildTransaction({'nonce': nonce}) + unsent_txn = MathContract.constructor().build_transaction({'nonce': nonce}) assert txn['data'] == unsent_txn['data'] new_txn_hash = w3.eth.send_transaction(unsent_txn) @@ -235,7 +235,7 @@ def test_contract_constructor_build_transaction_with_constructor_with_argument( txn = w3.eth.get_transaction(txn_hash) nonce = w3.eth.get_transaction_count(w3.eth.coinbase) unsent_txn = WithConstructorArgumentsContract.constructor( - *constructor_args, **constructor_kwargs).buildTransaction({'nonce': nonce}) + *constructor_args, **constructor_kwargs).build_transaction({'nonce': nonce}) assert txn['data'] == unsent_txn['data'] new_txn_hash = w3.eth.send_transaction(unsent_txn) diff --git a/tests/core/contracts/test_contract_estimateGas.py b/tests/core/contracts/test_contract_estimateGas.py index 704e746028..b704cb54cb 100644 --- a/tests/core/contracts/test_contract_estimateGas.py +++ b/tests/core/contracts/test_contract_estimateGas.py @@ -71,9 +71,9 @@ def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): return _payable_tester -def test_contract_estimateGas(w3, math_contract, estimateGas, transact): - gas_estimate = estimateGas(contract=math_contract, - contract_function='increment') +def test_contract_estimate_gas(w3, math_contract, estimate_gas, transact): + gas_estimate = estimate_gas(contract=math_contract, + contract_function='increment') txn_hash = transact( contract=math_contract, @@ -85,8 +85,8 @@ def test_contract_estimateGas(w3, math_contract, estimateGas, transact): assert abs(gas_estimate - gas_used) < 21000 -def test_contract_fallback_estimateGas(w3, fallback_function_contract): - gas_estimate = fallback_function_contract.fallback.estimateGas() +def test_contract_fallback_estimate_gas(w3, fallback_function_contract): + gas_estimate = fallback_function_contract.fallback.estimate_gas() txn_hash = fallback_function_contract.fallback.transact() @@ -96,10 +96,10 @@ def test_contract_fallback_estimateGas(w3, fallback_function_contract): assert abs(gas_estimate - gas_used) < 21000 -def test_contract_estimateGas_with_arguments(w3, math_contract, estimateGas, transact): - gas_estimate = estimateGas(contract=math_contract, - contract_function='add', - func_args=[5, 6]) +def test_contract_estimate_gas_with_arguments(w3, math_contract, estimate_gas, transact): + gas_estimate = estimate_gas(contract=math_contract, + contract_function='add', + func_args=[5, 6]) txn_hash = transact( contract=math_contract, @@ -111,13 +111,13 @@ def test_contract_estimateGas_with_arguments(w3, math_contract, estimateGas, tra assert abs(gas_estimate - gas_used) < 21000 -def test_estimateGas_not_sending_ether_to_nonpayable_function( +def test_estimate_gas_not_sending_ether_to_nonpayable_function( w3, payable_tester_contract, - estimateGas, + estimate_gas, transact): - gas_estimate = estimateGas(contract=payable_tester_contract, - contract_function='doNoValueCall') + gas_estimate = estimate_gas(contract=payable_tester_contract, + contract_function='doNoValueCall') txn_hash = transact( contract=payable_tester_contract, @@ -129,18 +129,18 @@ def test_estimateGas_not_sending_ether_to_nonpayable_function( assert abs(gas_estimate - gas_used) < 21000 -def test_estimateGas_sending_ether_to_nonpayable_function( +def test_estimate_gas_sending_ether_to_nonpayable_function( w3, payable_tester_contract, - estimateGas): + estimate_gas): with pytest.raises(ValidationError): - estimateGas(contract=payable_tester_contract, - contract_function='doNoValueCall', - tx_params={'value': 1}) + estimate_gas(contract=payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) -def test_estimateGas_accepts_latest_block(w3, math_contract, transact): - gas_estimate = math_contract.functions.counter().estimateGas(block_identifier='latest') +def test_estimate_gas_accepts_latest_block(w3, math_contract, transact): + gas_estimate = math_contract.functions.counter().estimate_gas(block_identifier='latest') txn_hash = transact( contract=math_contract, @@ -152,14 +152,14 @@ def test_estimateGas_accepts_latest_block(w3, math_contract, transact): assert abs(gas_estimate - gas_used) < 21000 -def test_estimateGas_block_identifier_unique_estimates(w3, math_contract, transact): +def test_estimate_gas_block_identifier_unique_estimates(w3, math_contract, transact): txn_hash = transact(contract=math_contract, contract_function="increment") w3.eth.wait_for_transaction_receipt(txn_hash) - latest_gas_estimate = math_contract.functions.counter().estimateGas( + latest_gas_estimate = math_contract.functions.counter().estimate_gas( block_identifier="latest" ) - earliest_gas_estimate = math_contract.functions.counter().estimateGas( + earliest_gas_estimate = math_contract.functions.counter().estimate_gas( block_identifier="earliest" ) diff --git a/tests/core/contracts/test_contract_method_abi_decoding.py b/tests/core/contracts/test_contract_method_abi_decoding.py index a6c0f1b6be..71ca09910e 100644 --- a/tests/core/contracts/test_contract_method_abi_decoding.py +++ b/tests/core/contracts/test_contract_method_abi_decoding.py @@ -88,7 +88,7 @@ def test_contract_abi_decoding(w3, abi, data, method, expected): assert params == expected reinvoke_func = contract.functions[func.fn_name](**params) - rebuild_txn = reinvoke_func.buildTransaction({'gas': 0, 'nonce': 0, 'to': '\x00' * 20}) + rebuild_txn = reinvoke_func.build_transaction({'gas': 0, 'nonce': 0, 'to': '\x00' * 20}) assert rebuild_txn['data'] == data @@ -115,5 +115,5 @@ def test_contract_abi_encoding_kwargs(w3, abi, method, expected, data): assert params == expected reinvoke_func = contract.functions[func.fn_name](**params) - rebuild_txn = reinvoke_func.buildTransaction({'gas': 0, 'nonce': 0, 'to': '\x00' * 20}) + rebuild_txn = reinvoke_func.build_transaction({'gas': 0, 'nonce': 0, 'to': '\x00' * 20}) assert rebuild_txn['data'] == data diff --git a/tests/core/contracts/test_contract_transact_interface.py b/tests/core/contracts/test_contract_transact_interface.py index 32a6c48414..05e88bc0d8 100644 --- a/tests/core/contracts/test_contract_transact_interface.py +++ b/tests/core/contracts/test_contract_transact_interface.py @@ -278,7 +278,7 @@ def test_auto_gas_computation_when_transacting(w3, assert deploy_receipt is not None string_contract = StringContract(address=deploy_receipt['contractAddress']) - gas_estimate = string_contract.functions.setValue(to_bytes(text="ÄLÄMÖLÖ")).estimateGas() + gas_estimate = string_contract.functions.setValue(to_bytes(text="ÄLÄMÖLÖ")).estimate_gas() # eth_abi will pass as raw bytes, no encoding # unless we encode ourselves From d4b96bfa15404fe4703076065caca6e857c56ae8 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 22 Apr 2022 06:02:50 -0400 Subject: [PATCH 38/39] removed buildTransaction and estimateGas --- web3/contract.py | 45 +++++++++------------------------------------ 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 8f89202685..8822ebd78e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -868,22 +868,6 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: built_transaction = self._build_transaction(transaction) return transactions.fill_transaction_defaults(self.w3, built_transaction) - @combomethod - @deprecated_for("build_transaction") - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - """ - Build the transaction dictionary without sending - """ - return self.build_transaction(transaction) - - @combomethod - @deprecated_for("estimate_gas") - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: - return self.estimate_gas(transaction, block_identifier) - @combomethod def estimate_gas( self, transaction: Optional[TxParams] = None, @@ -924,7 +908,7 @@ async def estimate_gas( class ConciseMethod: - ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} + ALLOWED_MODIFIERS = {'call', 'estimate_gas', 'transact', 'build_transaction'} def __init__( self, function: 'ContractFunction', @@ -1178,9 +1162,9 @@ def _estimate_gas( estimate_gas_transaction = cast(TxParams, dict(**transaction)) if 'data' in estimate_gas_transaction: - raise ValueError("Cannot set 'data' field in estimateGas transaction") + raise ValueError("Cannot set 'data' field in estimate_gas transaction") if 'to' in estimate_gas_transaction: - raise ValueError("Cannot set to in estimateGas transaction") + raise ValueError("Cannot set to in estimate_gas transaction") if self.address: estimate_gas_transaction.setdefault('to', self.address) @@ -1191,7 +1175,7 @@ def _estimate_gas( if 'to' not in estimate_gas_transaction: if isinstance(self, type): raise ValueError( - "When using `Contract.estimateGas` from a contract factory " + "When using `Contract.estimate_gas` from a contract factory " "you must provide a `to` address with the transaction" ) else: @@ -1211,7 +1195,7 @@ def _build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams if not self.address and 'to' not in built_transaction: raise ValueError( - "When using `ContractFunction.buildTransaction` from a contract factory " + "When using `ContractFunction.build_transaction` from a contract factory " "you must provide a `to` address with the transaction" ) if self.address and 'to' in built_transaction: @@ -1339,13 +1323,6 @@ def estimate_gas( **self.kwargs ) - @deprecated_for("estimate_gas") - def estimateGas( - self, transaction: Optional[TxParams] = None, - block_identifier: Optional[BlockIdentifier] = None - ) -> int: - return self.estimate_gas(transaction, block_identifier) - def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: built_transaction = self._build_transaction(transaction) @@ -1360,10 +1337,6 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: **self.kwargs ) - @deprecated_for("build_transaction") - def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: - return self.build_transaction(transaction) - class AsyncContractFunction(BaseContractFunction): @@ -2224,7 +2197,7 @@ def estimate_gas_for_function( **kwargs: Any) -> int: """Estimates gas cost a function call would take. - Don't call this directly, instead use :meth:`Contract.estimateGas` + Don't call this directly, instead use :meth:`Contract.estimate_gas` on your contract instance. """ estimate_transaction = prepare_transaction( @@ -2253,7 +2226,7 @@ async def async_estimate_gas_for_function( **kwargs: Any) -> int: """Estimates gas cost a function call would take. - Don't call this directly, instead use :meth:`Contract.estimateGas` + Don't call this directly, instead use :meth:`Contract.estimate_gas` on your contract instance. """ estimate_transaction = prepare_transaction( @@ -2281,7 +2254,7 @@ def build_transaction_for_function( **kwargs: Any) -> TxParams: """Builds a dictionary with the fields required to make the given transaction - Don't call this directly, instead use :meth:`Contract.buildTransaction` + Don't call this directly, instead use :meth:`Contract.build_transaction` on your contract instance. """ prepared_transaction = prepare_transaction( @@ -2311,7 +2284,7 @@ async def async_build_transaction_for_function( **kwargs: Any) -> TxParams: """Builds a dictionary with the fields required to make the given transaction - Don't call this directly, instead use :meth:`Contract.buildTransaction` + Don't call this directly, instead use :meth:`Contract.build_transaction` on your contract instance. """ prepared_transaction = prepare_transaction( From 4b52eac72ff31fdfb55ea04641cc37e5ce490b49 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 22 Apr 2022 21:14:31 -0400 Subject: [PATCH 39/39] docs update --- docs/contracts.rst | 22 ++-------------------- docs/examples.rst | 4 ++-- docs/web3.eth.account.rst | 2 +- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index b2e51ad935..b445624738 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -244,11 +244,6 @@ Each Contract Factory exposes the following methods. >>> txn_receipt['contractAddress'] '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318' -.. py:classmethod:: Contract.constructor(*args, **kwargs).estimateGas(transaction=None, block_identifier=None) - :noindex: - - .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).estimate_gas` - .. py:classmethod:: Contract.constructor(*args, **kwargs).estimate_gas(transaction=None, block_identifier=None) :noindex: @@ -272,11 +267,6 @@ Each Contract Factory exposes the following methods. >>> token_contract.constructor(web3.eth.coinbase, 12345).estimate_gas() 12563 -.. py:classmethod:: Contract.constructor(*args, **kwargs).buildTransaction(transaction=None) - :noindex: - - .. warning:: Deprecated: This method is deprecated in favor of :py:meth:`Contract.constructor(*args, **kwargs).build_transaction` - .. py:classmethod:: Contract.constructor(*args, **kwargs).build_transaction(transaction=None) :noindex: @@ -843,10 +833,6 @@ Methods a "missing trie node" error, because Ethereum node may have purged the past state from its database. `More information about archival nodes here `_. -.. py:method:: ContractFunction.estimateGas(transaction, block_identifier=None) - - .. warning:: Deprecated: This method is deprecated in favor of :class:`~estimate_gas` - .. py:method:: ContractFunction.estimate_gas(transaction, block_identifier=None) Call a contract function, executing the transaction locally using the @@ -874,10 +860,6 @@ Methods The parameter ``block_identifier`` is not enabled in geth nodes, hence passing a value of ``block_identifier`` when connected to a geth nodes would result in an error like: ``ValueError: {'code': -32602, 'message': 'too many arguments, want at most 1'}`` - -.. py:method:: ContractFunction.buildTransaction(transaction) - - .. warning:: Deprecated: This method is deprecated in favor of :class:`~build_transaction` .. py:method:: ContractFunction.build_transaction(transaction) @@ -941,7 +923,7 @@ Fallback Function Call fallback function, executing the transaction locally using the ``eth_call`` API. This will not create a new public transaction. -.. py:method:: Contract.fallback.estimateGas(transaction) +.. py:method:: Contract.fallback.estimate_gas(transaction) Call fallback function and return the gas estimation. @@ -949,7 +931,7 @@ Fallback Function Execute fallback function by sending a new public transaction. -.. py:method:: Contract.fallback.buildTransaction(transaction) +.. py:method:: Contract.fallback.build_transaction(transaction) Builds a transaction dictionary based on the contract fallback function call. diff --git a/docs/examples.rst b/docs/examples.rst index 83d41a6ee7..7488ba2b44 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -361,7 +361,7 @@ The following example demonstrates a few things: store_var_contract = w3.eth.contract(address=address, abi=contract_interface["abi"]) - gas_estimate = store_var_contract.functions.setVar(255).estimateGas() + gas_estimate = store_var_contract.functions.setVar(255).estimate_gas() print(f'Gas estimate to transact with setVar: {gas_estimate}') if gas_estimate < 100000: @@ -674,7 +674,7 @@ Just remember that you have to sign all transactions locally, as infura does not .. code-block:: python - transaction = contract.functions.function_Name(params).buildTransaction() + transaction = contract.functions.function_Name(params).build_transaction() transaction.update({ 'gas' : appropriate_gas_amount }) transaction.update({ 'nonce' : w3.eth.get_transaction_count('Your_Wallet_Address') }) signed_tx = w3.eth.account.sign_transaction(transaction, private_key) diff --git a/docs/web3.eth.account.rst b/docs/web3.eth.account.rst index a331c3b733..12e8d7872b 100644 --- a/docs/web3.eth.account.rst +++ b/docs/web3.eth.account.rst @@ -356,7 +356,7 @@ To sign a transaction locally that will invoke a smart contract: >>> unicorn_txn = unicorns.functions.transfer( ... '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359', ... 1, - ... ).buildTransaction({ + ... ).build_transaction({ ... 'chainId': 1, ... 'gas': 70000, ... 'maxFeePerGas': w3.toWei('2', 'gwei'),