From 0a898fe64a44d99a38ae5aa72d64d7893de2b4e0 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 3 Mar 2022 20:50:09 -0500 Subject: [PATCH 01/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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 067a14702236d97ce9d90a3d51f42e30bf53d055 Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Tue, 8 Mar 2022 15:50:04 -0700 Subject: [PATCH 38/73] 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 61f5f07414..13fda343b3 100644 --- a/web3/_utils/normalizers.py +++ b/web3/_utils/normalizers.py @@ -252,6 +252,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 754bcbebba7c82e27f6f8d3406c26c28cd00ccc1 Mon Sep 17 00:00:00 2001 From: AlwaysData Date: Wed, 9 Mar 2022 19:25:04 -0500 Subject: [PATCH 39/73] 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 91d01d5875..6501904d80 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 5679e5be86..79cb45e739 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 = f' str: return f'' -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 253f1141d1be08c7924d7353e6e2310b8a9fe442 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Fri, 11 Mar 2022 16:05:08 -0500 Subject: [PATCH 40/73] 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 465f640af6..30c1067b59 100644 --- a/ens/main.py +++ b/ens/main.py @@ -269,7 +269,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) @@ -449,7 +450,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 6501904d80..ab9d5abd12 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 4e67c7d1cd..1b87b48f7d 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 79cb45e739..9d2bb44355 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( f'Found multiple functions with matching {identifier}. ' diff --git a/web3/eth.py b/web3/eth.py index 54c88a41d1..91af703ef9 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 @@ -561,7 +589,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: @@ -956,35 +983,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 e117b6b733c61ed5866dca28ca127eff91817cf8 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 15 Mar 2022 16:52:37 -0400 Subject: [PATCH 41/73] 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 9d2bb44355..4faa049437 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 17cac586deaa13cce7adb9940567e091e877e453 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 22 Mar 2022 21:03:17 -0400 Subject: [PATCH 42/73] 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 4faa049437..8f89202685 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 bfd4f6099e6428a398b5a02e6d515779efcb8b01 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Thu, 24 Mar 2022 16:33:05 -0400 Subject: [PATCH 43/73] 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 30c1067b59..0db30d7401 100644 --- a/ens/main.py +++ b/ens/main.py @@ -270,7 +270,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) @@ -451,7 +451,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 bbac8666545a63d7438bbce71d26ab5bb6a52d49 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Fri, 25 Mar 2022 18:52:19 -0400 Subject: [PATCH 44/73] Feature/asyncify contract (#2405) * renaming deprecated methods --- ens/main.py | 4 +- ethpm/package.py | 2 +- 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 +- web3/eth.py | 37 +----------- 11 files changed, 77 insertions(+), 112 deletions(-) diff --git a/ens/main.py b/ens/main.py index 0db30d7401..30c1067b59 100644 --- a/ens/main.py +++ b/ens/main.py @@ -270,7 +270,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) @@ -451,7 +451,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/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 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 d4b96bfa15404fe4703076065caca6e857c56ae8 Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 22 Apr 2022 06:02:50 -0400 Subject: [PATCH 45/73] 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 46/73] 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'), From 54e7bb327af456e903c3f35d6435831f12e8a48d Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Sat, 23 Apr 2022 03:57:43 -0400 Subject: [PATCH 47/73] Feature/asyncify contract (#2439) * removed buildTransaction and estimateGas --- docs/contracts.rst | 22 ++----------------- docs/examples.rst | 4 ++-- docs/web3.eth.account.rst | 2 +- web3/contract.py | 45 ++++++++------------------------------- 4 files changed, 14 insertions(+), 59 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'), 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 655f0dcaa61a27a7ce57f3165ab362abe456d26b Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Tue, 8 Mar 2022 15:50:04 -0700 Subject: [PATCH 48/73] 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 61f5f07414..13fda343b3 100644 --- a/web3/_utils/normalizers.py +++ b/web3/_utils/normalizers.py @@ -252,6 +252,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 543b9dd5b4095be63bdd4740fd44398fcd86770f Mon Sep 17 00:00:00 2001 From: AlwaysData Date: Wed, 9 Mar 2022 19:25:04 -0500 Subject: [PATCH 49/73] 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 91d01d5875..6501904d80 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 5679e5be86..79cb45e739 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 = f' str: return f'' -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 310101017fc57a0a4f2dd2e6ecd673e48ea8530b Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Fri, 11 Mar 2022 16:05:08 -0500 Subject: [PATCH 50/73] 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 465f640af6..30c1067b59 100644 --- a/ens/main.py +++ b/ens/main.py @@ -269,7 +269,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) @@ -449,7 +450,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 6501904d80..ab9d5abd12 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 4e67c7d1cd..1b87b48f7d 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 79cb45e739..9d2bb44355 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( f'Found multiple functions with matching {identifier}. ' diff --git a/web3/eth.py b/web3/eth.py index 54c88a41d1..91af703ef9 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 @@ -561,7 +589,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: @@ -956,35 +983,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 009723eab0166a18d6d180322fa23fd4a574c8bd Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 15 Mar 2022 16:52:37 -0400 Subject: [PATCH 51/73] 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 9d2bb44355..4faa049437 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 bdf06c4157c171be7da3aed1415264b442d1462d Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 22 Mar 2022 21:03:17 -0400 Subject: [PATCH 52/73] 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 4faa049437..8f89202685 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 fef9e4308816d0d5147b489530af76effb96c39a Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Thu, 24 Mar 2022 16:33:05 -0400 Subject: [PATCH 53/73] 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 30c1067b59..0db30d7401 100644 --- a/ens/main.py +++ b/ens/main.py @@ -270,7 +270,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) @@ -451,7 +451,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 5887604fdadee652535aa0b7aee63de4ee68816d Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Fri, 25 Mar 2022 18:52:19 -0400 Subject: [PATCH 54/73] Feature/asyncify contract (#2405) * renaming deprecated methods --- ens/main.py | 4 +- ethpm/package.py | 2 +- 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 +- web3/eth.py | 37 +----------- 11 files changed, 77 insertions(+), 112 deletions(-) diff --git a/ens/main.py b/ens/main.py index 0db30d7401..30c1067b59 100644 --- a/ens/main.py +++ b/ens/main.py @@ -270,7 +270,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) @@ -451,7 +451,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/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 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 a5b8a04bc2c6b247c2456efe6d28ce15016e7a84 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Sat, 23 Apr 2022 03:57:43 -0400 Subject: [PATCH 55/73] Feature/asyncify contract (#2439) * removed buildTransaction and estimateGas --- docs/contracts.rst | 22 ++----------------- docs/examples.rst | 4 ++-- docs/web3.eth.account.rst | 2 +- web3/contract.py | 45 ++++++++------------------------------- 4 files changed, 14 insertions(+), 59 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'), 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 12dc8b47037049c9b326bfc87bc61982462242bf Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Mon, 25 Apr 2022 09:20:16 -0600 Subject: [PATCH 56/73] [WIP] Test asyncify contract (#2422) * add async tests for asynced contract functions --- conftest.py | 43 + tests/core/conftest.py | 10 + tests/core/contracts/conftest.py | 412 +++++++- tests/core/contracts/test_concise_contract.py | 13 +- .../test_contract_buildTransaction.py | 249 ++++- .../contracts/test_contract_call_interface.py | 938 ++++++++++++++++-- .../test_contract_caller_interface.py | 186 +++- .../contracts/test_contract_constructor.py | 260 +++++ .../contracts/test_contract_deployment.py | 109 ++ .../contracts/test_contract_estimateGas.py | 185 ++-- tests/core/contracts/test_contract_example.py | 109 ++ .../test_contract_transact_interface.py | 356 ++++++- .../contracts/test_contract_util_functions.py | 99 +- tests/core/contracts/utils.py | 39 + .../providers/test_async_tester_provider.py | 25 + .../core/utilities/test_async_transaction.py | 56 ++ web3/contract.py | 14 +- 17 files changed, 2791 insertions(+), 312 deletions(-) create mode 100644 tests/core/contracts/utils.py create mode 100644 tests/core/providers/test_async_tester_provider.py diff --git a/conftest.py b/conftest.py index 17ccc39dd6..802ce8d393 100644 --- a/conftest.py +++ b/conftest.py @@ -9,6 +9,7 @@ Web3, ) from web3.providers.eth_tester import ( + AsyncEthereumTesterProvider, EthereumTesterProvider, ) @@ -44,6 +45,10 @@ def is_testrpc_provider(provider): return isinstance(provider, EthereumTesterProvider) +def is_async_testrpc_provider(provider): + return isinstance(provider, AsyncEthereumTesterProvider) + + @pytest.fixture() def skip_if_testrpc(): @@ -53,6 +58,15 @@ def _skip_if_testrpc(w3): return _skip_if_testrpc +@pytest.fixture() +def async_skip_if_testrpc(): + + def _skip_if_testrpc(async_w3): + if is_async_testrpc_provider(async_w3.provider): + pytest.skip() + return _skip_if_testrpc + + @pytest.fixture() def wait_for_miner_start(): def _wait_for_miner_start(w3, timeout=60): @@ -77,6 +91,19 @@ def _wait_for_block(w3, block_number=1, timeout=None): return _wait_for_block +@pytest.fixture(scope="module") +def async_wait_for_block(): + async def _wait_for_block(w3, block_number=1, timeout=None): + if not timeout: + timeout = (block_number - w3.eth.block_number) * 3 + poll_delay_counter = PollDelayCounter() + with Timeout(timeout) as timeout: + while w3.eth.block_number < block_number: + w3.manager.request_blocking("evm_mine", []) + timeout.sleep(poll_delay_counter()) + return _wait_for_block + + @pytest.fixture(scope="module") def wait_for_transaction(): def _wait_for_transaction(w3, txn_hash, timeout=120): @@ -93,6 +120,22 @@ def _wait_for_transaction(w3, txn_hash, timeout=120): return _wait_for_transaction +@pytest.fixture(scope="module") +def async_wait_for_transaction(): + async def _wait_for_transaction(w3, txn_hash, timeout=120): + poll_delay_counter = PollDelayCounter() + with Timeout(timeout) as timeout: + while True: + txn_receipt = await w3.eth.get_transaction_receipt(txn_hash) + if txn_receipt is not None: + break + time.sleep(poll_delay_counter()) + timeout.check() + + return txn_receipt + return _wait_for_transaction + + @pytest.fixture() def w3(): provider = EthereumTesterProvider() diff --git a/tests/core/conftest.py b/tests/core/conftest.py index ee8a81b91a..dddadff173 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -115,3 +115,13 @@ async def async_w3(): middlewares=provider.middlewares) w3.eth.default_account = await w3.eth.coinbase return w3 + + +@pytest_asyncio.fixture() +async def async_w3_strict_abi(): + provider = AsyncEthereumTesterProvider() + w3 = Web3(provider, modules={'eth': [AsyncEth]}, + middlewares=provider.middlewares) + w3.enable_strict_bytes_type_checking() + 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 953b866aec..c6ce607584 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -5,11 +5,13 @@ from eth_utils import ( event_signature_to_log_topic, ) -from eth_utils.toolz import ( - identity, -) import pytest_asyncio +from utils import ( + async_deploy, + async_partial, + deploy, +) from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, CONTRACT_EMITTER_CODE, @@ -50,9 +52,6 @@ REVERT_CONTRACT_BYTECODE, REVERT_CONTRACT_RUNTIME_CODE, ) -from web3.contract import ( - AsyncContract, -) CONTRACT_NESTED_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; @@ -102,6 +101,11 @@ def NestedTupleContract(w3, NESTED_TUPLE_CONTRACT): return w3.eth.contract(**NESTED_TUPLE_CONTRACT) +@pytest.fixture() +def AsyncNestedTupleContract(async_w3, NESTED_TUPLE_CONTRACT): + return async_w3.eth.contract(**NESTED_TUPLE_CONTRACT) + + CONTRACT_TUPLE_SOURCE = """ pragma solidity >=0.4.19 <0.6.0; pragma experimental ABIEncoderV2; @@ -149,6 +153,11 @@ def TupleContract(w3, TUPLE_CONTRACT): return w3.eth.contract(**TUPLE_CONTRACT) +@pytest.fixture() +def AsyncTupleContract(async_w3, TUPLE_CONTRACT): + return async_w3.eth.contract(**TUPLE_CONTRACT) + + CONTRACT_CODE = "0x606060405261022e806100126000396000f360606040523615610074576000357c01000000000000000000000000000000000000000000000000000000009004806316216f391461007657806361bc221a146100995780637cf5dab0146100bc578063a5f3c23b146100e8578063d09de08a1461011d578063dcf537b11461014057610074565b005b610083600480505061016c565b6040518082815260200191505060405180910390f35b6100a6600480505061017f565b6040518082815260200191505060405180910390f35b6100d26004808035906020019091905050610188565b6040518082815260200191505060405180910390f35b61010760048080359060200190919080359060200190919050506101ea565b6040518082815260200191505060405180910390f35b61012a6004805050610201565b6040518082815260200191505060405180910390f35b6101566004808035906020019091905050610217565b6040518082815260200191505060405180910390f35b6000600d9050805080905061017c565b90565b60006000505481565b6000816000600082828250540192505081905550600060005054905080507f3496c3ede4ec3ab3686712aa1c238593ea6a42df83f98a5ec7df9834cfa577c5816040518082815260200191505060405180910390a18090506101e5565b919050565b6000818301905080508090506101fb565b92915050565b600061020d6001610188565b9050610214565b90565b60006007820290508050809050610229565b91905056" # noqa: E501 @@ -182,6 +191,24 @@ def MathContract(w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): ) +@pytest_asyncio.fixture() +def AsyncMathContract(async_w3, MATH_ABI, MATH_CODE, MATH_RUNTIME): + return async_w3.eth.contract( + abi=MATH_ABI, + bytecode=MATH_CODE, + bytecode_runtime=MATH_RUNTIME) + + +@pytest.fixture() +def math_contract(w3, MathContract, address_conversion_func): + return deploy(w3, MathContract, address_conversion_func) + + +@pytest_asyncio.fixture() +async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): + return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) + + CONTRACT_SIMPLE_CONSTRUCTOR_CODE = '0x60606040526003600055602c8060156000396000f3606060405260e060020a600035046373d4a13a8114601a575b005b602260005481565b6060908152602090f3' # noqa: E501 CONTRACT_SIMPLE_CONSTRUCTOR_RUNTIME = '0x606060405260e060020a600035046373d4a13a8114601a575b005b602260005481565b6060908152602090f3' # noqa: E501 CONTRACT_SIMPLE_CONSTRUCTOR_ABI = json.loads('[{"constant":true,"inputs":[],"name":"data","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[],"type":"constructor"}]') # noqa: E501 @@ -258,6 +285,42 @@ def WithConstructorArgumentsContractStrict(w3_strict_abi, ) +@pytest.fixture() +def AsyncSimpleConstructorContract(async_w3, + SIMPLE_CONSTRUCTOR_CODE, + SIMPLE_CONSTRUCTOR_RUNTIME, + SIMPLE_CONSTRUCTOR_ABI): + return async_w3.eth.contract( + abi=SIMPLE_CONSTRUCTOR_ABI, + bytecode=SIMPLE_CONSTRUCTOR_CODE, + bytecode_runtime=SIMPLE_CONSTRUCTOR_RUNTIME, + ) + + +@pytest.fixture() +def AsyncWithConstructorArgumentsContract(async_w3, + WITH_CONSTRUCTOR_ARGUMENTS_CODE, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + WITH_CONSTRUCTOR_ARGUMENTS_ABI): + return async_w3.eth.contract( + abi=WITH_CONSTRUCTOR_ARGUMENTS_ABI, + bytecode=WITH_CONSTRUCTOR_ARGUMENTS_CODE, + bytecode_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + ) + + +@pytest.fixture() +def AsyncWithConstructorArgumentsContractStrict(async_w3_strict_abi, + WITH_CONSTRUCTOR_ARGUMENTS_CODE, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + WITH_CONSTRUCTOR_ARGUMENTS_ABI): + return async_w3_strict_abi.eth.contract( + abi=WITH_CONSTRUCTOR_ARGUMENTS_ABI, + bytecode=WITH_CONSTRUCTOR_ARGUMENTS_CODE, + bytecode_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + ) + + CONTRACT_WITH_CONSTRUCTOR_ADDRESS_CODE = "0x6060604052604051602080607683395060806040525160008054600160a060020a031916821790555060428060346000396000f3606060405260e060020a600035046334664e3a8114601a575b005b603860005473ffffffffffffffffffffffffffffffffffffffff1681565b6060908152602090f3" # noqa: E501 CONTRACT_WITH_CONSTRUCTOR_ADDRESS_RUNTIME = "0x606060405260e060020a600035046334664e3a8114601a575b005b603860005473ffffffffffffffffffffffffffffffffffffffff1681565b6060908152602090f3" # noqa: E501 CONTRACT_WITH_CONSTRUCTOR_ADDRESS_ABI = json.loads('[{"constant":true,"inputs":[],"name":"testAddr","outputs":[{"name":"","type":"address"}],"type":"function"},{"inputs":[{"name":"_testAddr","type":"address"}],"type":"constructor"}]') # noqa: E501 @@ -290,15 +353,79 @@ def WithConstructorAddressArgumentsContract(w3, ) +@pytest.fixture() +def AsyncWithConstructorAddressArgumentsContract(async_w3, + WITH_CONSTRUCTOR_ADDRESS_CODE, + WITH_CONSTRUCTOR_ADDRESS_RUNTIME, + WITH_CONSTRUCTOR_ADDRESS_ABI): + return async_w3.eth.contract( + abi=WITH_CONSTRUCTOR_ADDRESS_ABI, + bytecode=WITH_CONSTRUCTOR_ADDRESS_CODE, + bytecode_runtime=WITH_CONSTRUCTOR_ADDRESS_RUNTIME, + ) + + +@pytest.fixture() +def address_contract(w3, WithConstructorAddressArgumentsContract, address_conversion_func): + return deploy( + w3, + WithConstructorAddressArgumentsContract, + address_conversion_func, + args=["0xd3CdA913deB6f67967B99D67aCDFa1712C293601"] + ) + + +@pytest_asyncio.fixture() +async def async_address_contract( + async_w3, + AsyncWithConstructorAddressArgumentsContract, + address_conversion_func): + return await async_deploy( + async_w3, + AsyncWithConstructorAddressArgumentsContract, + address_conversion_func, + args=["0xd3CdA913deB6f67967B99D67aCDFa1712C293601"] + ) + + +CONTRACT_ADDRESS_REFLECTOR_CODE = "6060604052341561000f57600080fd5b6101ca8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630b816c1614610048578063c04d11fc146100c157600080fd5b341561005357600080fd5b61007f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610170565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100cc57600080fd5b61011960048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061017a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561015c578082015181840152602081019050610141565b505050509050019250505060405180910390f35b6000819050919050565b61018261018a565b819050919050565b6020604051908101604052806000815250905600a165627a7a723058206b15d98a803b91327d94f943e9712291539701b2f7370e10f5873633941484930029" # noqa: 501 + +CONTRACT_ADDRESS_REFLECTOR_RUNTIME = "60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630b816c1614610048578063c04d11fc146100c157600080fd5b341561005357600080fd5b61007f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610170565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100cc57600080fd5b61011960048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061017a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561015c578082015181840152602081019050610141565b505050509050019250505060405180910390f35b6000819050919050565b61018261018a565b819050919050565b6020604051908101604052806000815250905600a165627a7a723058206b15d98a803b91327d94f943e9712291539701b2f7370e10f5873633941484930029" # noqa: 501 + +CONTRACT_ADDRESS_REFLECTOR_ABI = json.loads('[{"constant":true,"inputs":[{"name":"arg","type":"address"}],"name":"reflect","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"arg","type":"address[]"}],"name":"reflect","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"pure","type":"function"}]') # noqa: 501 + + @pytest.fixture() def AddressReflectorContract(w3): return w3.eth.contract( - abi=json.loads('[{"constant":true,"inputs":[{"name":"arg","type":"address"}],"name":"reflect","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"arg","type":"address[]"}],"name":"reflect","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"pure","type":"function"}]'), # noqa: 501 - bytecode="6060604052341561000f57600080fd5b6101ca8061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630b816c1614610048578063c04d11fc146100c157600080fd5b341561005357600080fd5b61007f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610170565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100cc57600080fd5b61011960048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061017a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561015c578082015181840152602081019050610141565b505050509050019250505060405180910390f35b6000819050919050565b61018261018a565b819050919050565b6020604051908101604052806000815250905600a165627a7a723058206b15d98a803b91327d94f943e9712291539701b2f7370e10f5873633941484930029", # noqa: 501 - bytecode_runtime="60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630b816c1614610048578063c04d11fc146100c157600080fd5b341561005357600080fd5b61007f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610170565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100cc57600080fd5b61011960048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061017a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561015c578082015181840152602081019050610141565b505050509050019250505060405180910390f35b6000819050919050565b61018261018a565b819050919050565b6020604051908101604052806000815250905600a165627a7a723058206b15d98a803b91327d94f943e9712291539701b2f7370e10f5873633941484930029", # noqa: 501 + abi=CONTRACT_ADDRESS_REFLECTOR_ABI, + bytecode=CONTRACT_ADDRESS_REFLECTOR_CODE, + bytecode_runtime=CONTRACT_ADDRESS_REFLECTOR_RUNTIME + ) + + +@pytest.fixture() +def address_reflector_contract(w3, AddressReflectorContract, address_conversion_func): + return deploy(w3, AddressReflectorContract, address_conversion_func) + + +@pytest.fixture() +def AsyncAddressReflectorContract(async_w3): + return async_w3.eth.contract( + abi=CONTRACT_ADDRESS_REFLECTOR_ABI, + bytecode=CONTRACT_ADDRESS_REFLECTOR_CODE, + bytecode_runtime=CONTRACT_ADDRESS_REFLECTOR_RUNTIME ) +@pytest_asyncio.fixture() +async def async_address_reflector_contract( + async_w3, + AsyncAddressReflectorContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncAddressReflectorContract, address_conversion_func) + + CONTRACT_STRING_CODE = "0x6060604052604051610496380380610496833981016040528051018060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10608d57805160ff19168380011785555b50607c9291505b8082111560ba57838155600101606b565b5050506103d8806100be6000396000f35b828001600101855582156064579182015b828111156064578251826000505591602001919060010190609e565b509056606060405260e060020a600035046320965255811461003c57806330de3cee1461009f5780633fa4f245146100c457806393a0935214610121575b005b6101c7600060608181528154602060026001831615610100026000190190921691909104601f810182900490910260a0908101604052608082815292939190828280156102605780601f1061023557610100808354040283529160200191610260565b6101c7600060609081526101a06040526101006080818152906102d860a03990505b90565b6101c760008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102975780601f1061026c57610100808354040283529160200191610297565b60206004803580820135601f81018490049093026080908101604052606084815261003a946024939192918401918190838280828437509496505050505050508060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061029f57805160ff19168380011785555b506102cf9291505b808211156102d4578381556001016101b4565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156102275780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b820191906000526020600020905b81548152906001019060200180831161024357829003601f168201915b505050505090506100c1565b820191906000526020600020905b81548152906001019060200180831161027a57829003601f168201915b505050505081565b828001600101855582156101ac579182015b828111156101ac5782518260005055916020019190600101906102b1565b505050565b509056000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" # noqa: E501 CONTRACT_STRING_RUNTIME = "0x606060405260e060020a600035046320965255811461003c57806330de3cee1461009f5780633fa4f245146100c457806393a0935214610121575b005b6101c7600060608181528154602060026001831615610100026000190190921691909104601f810182900490910260a0908101604052608082815292939190828280156102605780601f1061023557610100808354040283529160200191610260565b6101c7600060609081526101a06040526101006080818152906102d860a03990505b90565b6101c760008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102975780601f1061026c57610100808354040283529160200191610297565b60206004803580820135601f81018490049093026080908101604052606084815261003a946024939192918401918190838280828437509496505050505050508060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061029f57805160ff19168380011785555b506102cf9291505b808211156102d4578381556001016101b4565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156102275780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b820191906000526020600020905b81548152906001019060200180831161024357829003601f168201915b505050505090506100c1565b820191906000526020600020905b81548152906001019060200180831161027a57829003601f168201915b505050505081565b828001600101855582156101ac579182015b828111156101ac5782518260005055916020019190600101906102b1565b505050565b509056000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" # noqa: E501 @@ -335,6 +462,27 @@ def StringContract(w3, STRING_CONTRACT): return w3.eth.contract(**STRING_CONTRACT) +@pytest.fixture() +def AsyncStringContract(async_w3, STRING_CONTRACT): + return async_w3.eth.contract(**STRING_CONTRACT) + + +@pytest.fixture() +def string_contract(w3, StringContract, address_conversion_func): + return deploy(w3, StringContract, address_conversion_func, args=["Caqalai"]) + + +@pytest_asyncio.fixture() +async def async_string_contract( + async_w3, + AsyncStringContract, + address_conversion_func): + return await async_deploy(async_w3, + AsyncStringContract, + address_conversion_func, + args=["Caqalai"]) + + CONTRACT_BYTES_CODE = "60606040526040805190810160405280600281526020017f01230000000000000000000000000000000000000000000000000000000000008152506000908051906020019061004f929190610096565b50341561005b57600080fd5b604051610723380380610723833981016040528080518201919050505b806001908051906020019061008e929190610116565b505b506101bb565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100d757805160ff1916838001178555610105565b82800160010185558215610105579182015b828111156101045782518255916020019190600101906100e9565b5b5090506101129190610196565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015757805160ff1916838001178555610185565b82800160010185558215610185579182015b82811115610184578251825591602001919060010190610169565b5b5090506101929190610196565b5090565b6101b891905b808211156101b457600081600090555060010161019c565b5090565b90565b610559806101ca6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551461005f57806330de3cee146100ee5780633fa4f2451461017d578063439970aa1461020c575b600080fd5b341561006a57600080fd5b610072610269565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100b35780820151818401525b602081019050610097565b50505050905090810190601f1680156100e05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100f957600080fd5b610101610312565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101425780820151818401525b602081019050610126565b50505050905090810190601f16801561016f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561018857600080fd5b6101906103bb565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101d15780820151818401525b6020810190506101b5565b50505050905090810190601f1680156101fe5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561021757600080fd5b610267600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610459565b005b610271610474565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103075780601f106102dc57610100808354040283529160200191610307565b820191906000526020600020905b8154815290600101906020018083116102ea57829003601f168201915b505050505090505b90565b61031a610474565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103b05780601f10610385576101008083540402835291602001916103b0565b820191906000526020600020905b81548152906001019060200180831161039357829003601f168201915b505050505090505b90565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104515780601f1061042657610100808354040283529160200191610451565b820191906000526020600020905b81548152906001019060200180831161043457829003601f168201915b505050505081565b806001908051906020019061046f929190610488565b505b50565b602060405190810160405280600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106104c957805160ff19168380011785556104f7565b828001600101855582156104f7579182015b828111156104f65782518255916020019190600101906104db565b5b5090506105049190610508565b5090565b61052a91905b8082111561052657600081600090555060010161050e565b5090565b905600a165627a7a723058203ff916ee91add6247b20793745d1c6a8d8dcaa49d8c84fbbabb5c966fd9b6fc90029" # noqa: E501 CONTRACT_BYTES_RUNTIME = "60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551461005f57806330de3cee146100ee5780633fa4f2451461017d578063439970aa1461020c575b600080fd5b341561006a57600080fd5b610072610269565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100b35780820151818401525b602081019050610097565b50505050905090810190601f1680156100e05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100f957600080fd5b610101610312565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101425780820151818401525b602081019050610126565b50505050905090810190601f16801561016f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561018857600080fd5b6101906103bb565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101d15780820151818401525b6020810190506101b5565b50505050905090810190601f1680156101fe5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561021757600080fd5b610267600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610459565b005b610271610474565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103075780601f106102dc57610100808354040283529160200191610307565b820191906000526020600020905b8154815290600101906020018083116102ea57829003601f168201915b505050505090505b90565b61031a610474565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103b05780601f10610385576101008083540402835291602001916103b0565b820191906000526020600020905b81548152906001019060200180831161039357829003601f168201915b505050505090505b90565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104515780601f1061042657610100808354040283529160200191610451565b820191906000526020600020905b81548152906001019060200180831161043457829003601f168201915b505050505081565b806001908051906020019061046f929190610488565b505b50565b602060405190810160405280600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106104c957805160ff19168380011785556104f7565b828001600101855582156104f7579182015b828111156104f65782518255916020019190600101906104db565b5b5090506105049190610508565b5090565b61052a91905b8082111561052657600081600090555060010161050e565b5090565b905600a165627a7a723058203ff916ee91add6247b20793745d1c6a8d8dcaa49d8c84fbbabb5c966fd9b6fc90029" # noqa: E501 @@ -371,6 +519,11 @@ def BytesContract(w3, BYTES_CONTRACT): return w3.eth.contract(**BYTES_CONTRACT) +@pytest.fixture() +def AsyncBytesContract(async_w3, BYTES_CONTRACT): + return async_w3.eth.contract(**BYTES_CONTRACT) + + CONTRACT_BYTES32_CODE = "60606040527f0123012301230123012301230123012301230123012301230123012301230123600090600019169055341561003957600080fd5b6040516020806101e2833981016040528080519060200190919050505b80600181600019169055505b505b61016f806100736000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551461005f57806330de3cee146100905780633fa4f245146100c157806358825b10146100f2575b600080fd5b341561006a57600080fd5b610072610119565b60405180826000191660001916815260200191505060405180910390f35b341561009b57600080fd5b6100a3610124565b60405180826000191660001916815260200191505060405180910390f35b34156100cc57600080fd5b6100d461012e565b60405180826000191660001916815260200191505060405180910390f35b34156100fd57600080fd5b610117600480803560001916906020019091905050610134565b005b600060015490505b90565b6000805490505b90565b60015481565b80600181600019169055505b505600a165627a7a7230582043b15c20378b1603d330561258ccf291d08923a4c25fa8af0d590a010a6322180029" # noqa: E501 CONTRACT_BYTES32_RUNTIME = "60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063209652551461005f57806330de3cee146100905780633fa4f245146100c157806358825b10146100f2575b600080fd5b341561006a57600080fd5b610072610119565b60405180826000191660001916815260200191505060405180910390f35b341561009b57600080fd5b6100a3610124565b60405180826000191660001916815260200191505060405180910390f35b34156100cc57600080fd5b6100d461012e565b60405180826000191660001916815260200191505060405180910390f35b34156100fd57600080fd5b610117600480803560001916906020019091905050610134565b005b600060015490505b90565b6000805490505b90565b60015481565b80600181600019169055505b505600a165627a7a7230582043b15c20378b1603d330561258ccf291d08923a4c25fa8af0d590a010a6322180029" # noqa: E501 @@ -407,6 +560,11 @@ def Bytes32Contract(w3, BYTES32_CONTRACT): return w3.eth.contract(**BYTES32_CONTRACT) +@pytest.fixture() +def AsyncBytes32Contract(async_w3, BYTES32_CONTRACT): + return async_w3.eth.contract(**BYTES32_CONTRACT) + + @pytest.fixture() def EMITTER_CODE(): return CONTRACT_EMITTER_CODE @@ -665,11 +823,81 @@ def ArraysContract(w3, ARRAYS_CONTRACT): return w3.eth.contract(**ARRAYS_CONTRACT) +@pytest.fixture() +def AsyncArraysContract(async_w3, ARRAYS_CONTRACT): + return async_w3.eth.contract(**ARRAYS_CONTRACT) + + +@pytest.fixture() +def arrays_contract(w3, ArraysContract, address_conversion_func): + # bytes_32 = [keccak('0'), keccak('1')] + bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + return deploy(w3, ArraysContract, address_conversion_func, args=[bytes32_array, byte_arr]) + + +@pytest_asyncio.fixture() +async def async_arrays_contract(async_w3, AsyncArraysContract, address_conversion_func): + # bytes_32 = [keccak('0'), keccak('1')] + bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + return await async_deploy(async_w3, + AsyncArraysContract, + address_conversion_func, + args=[bytes32_array, byte_arr]) + + @pytest.fixture() def StrictArraysContract(w3_strict_abi, ARRAYS_CONTRACT): return w3_strict_abi.eth.contract(**ARRAYS_CONTRACT) +@pytest.fixture() +def AsyncStrictArraysContract(async_w3_strict_abi, ARRAYS_CONTRACT): + return async_w3_strict_abi.eth.contract(**ARRAYS_CONTRACT) + + +@pytest.fixture() +def strict_arrays_contract(w3_strict_abi, StrictArraysContract, address_conversion_func): + # bytes_32 = [keccak('0'), keccak('1')] + bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + return deploy( + w3_strict_abi, + StrictArraysContract, + address_conversion_func, + args=[bytes32_array, byte_arr] + ) + + +@pytest_asyncio.fixture() +async def async_strict_arrays_contract( + async_w3_strict_abi, + AsyncStrictArraysContract, + address_conversion_func): + # bytes_32 = [keccak('0'), keccak('1')] + bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + return await async_deploy( + async_w3_strict_abi, + AsyncStrictArraysContract, + address_conversion_func, + args=[bytes32_array, byte_arr] + ) + + CONTRACT_PAYABLE_TESTER_SOURCE = """ contract PayableTester { bool public wasCalled; @@ -718,6 +946,24 @@ def PayableTesterContract(w3, PAYABLE_TESTER_CONTRACT): return w3.eth.contract(**PAYABLE_TESTER_CONTRACT) +@pytest.fixture() +def AsyncPayableTesterContract(async_w3, PAYABLE_TESTER_CONTRACT): + return async_w3.eth.contract(**PAYABLE_TESTER_CONTRACT) + + +@pytest.fixture() +def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): + return deploy(w3, PayableTesterContract, address_conversion_func) + + +@pytest_asyncio.fixture() +async def async_payable_tester_contract( + async_w3, + AsyncPayableTesterContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncPayableTesterContract, address_conversion_func) + + # no matter the function selector, this will return back the 32 bytes of data supplied CONTRACT_REFLECTION_CODE = ( "0x610011566020600460003760206000f3005b61000461001103610004600039610004610011036000f3" @@ -763,6 +1009,11 @@ def FixedReflectionContract(w3): return w3.eth.contract(abi=CONTRACT_FIXED_ABI, bytecode=CONTRACT_REFLECTION_CODE) +@pytest.fixture +def AsyncFixedReflectionContract(async_w3): + return async_w3.eth.contract(abi=CONTRACT_FIXED_ABI, bytecode=CONTRACT_REFLECTION_CODE) + + @pytest.fixture() def FALLBACK_FUNCTION_CODE(): return CONTRACT_FALLBACK_FUNCTION_CODE @@ -794,6 +1045,24 @@ def FallbackFunctionContract(w3, FALLBACK_FUNCTION_CONTRACT): return w3.eth.contract(**FALLBACK_FUNCTION_CONTRACT) +@pytest.fixture() +def AsyncFallbackFunctionContract(async_w3, FALLBACK_FUNCTION_CONTRACT): + return async_w3.eth.contract(**FALLBACK_FUNCTION_CONTRACT) + + +@pytest.fixture() +def fallback_function_contract(w3, FallbackFunctionContract, address_conversion_func): + return deploy(w3, FallbackFunctionContract, address_conversion_func) + + +@pytest_asyncio.fixture() +async def async_fallback_function_contract( + async_w3, + AsyncFallbackFunctionContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncFallbackFunctionContract, address_conversion_func) + + @pytest.fixture() def RECEIVE_FUNCTION_CODE(): return CONTRACT_RECEIVE_FUNCTION_CODE @@ -820,11 +1089,6 @@ def RECEIVE_FUNCTION_CONTRACT(RECEIVE_FUNCTION_CODE, } -@pytest.fixture() -def NoReceiveFunctionContract(w3, NO_RECEIVE_FUNCTION_CONTRACT): - return w3.eth.contract(**NO_RECEIVE_FUNCTION_CONTRACT) - - @pytest.fixture() def NO_RECEIVE_FUNCTION_CODE(): return CONTRACT_NO_RECEIVE_FUNCTION_CODE @@ -851,11 +1115,52 @@ def NO_RECEIVE_FUNCTION_CONTRACT(NO_RECEIVE_FUNCTION_CODE, } +@pytest.fixture() +def NoReceiveFunctionContract(w3, NO_RECEIVE_FUNCTION_CONTRACT): + return w3.eth.contract(**NO_RECEIVE_FUNCTION_CONTRACT) + + +@pytest.fixture() +def AsyncNoReceiveFunctionContract(async_w3, NO_RECEIVE_FUNCTION_CONTRACT): + return async_w3.eth.contract(**NO_RECEIVE_FUNCTION_CONTRACT) + + +@pytest.fixture() +def no_receive_function_contract(w3, NoReceiveFunctionContract, address_conversion_func): + return deploy(w3, NoReceiveFunctionContract, address_conversion_func) + + +@pytest_asyncio.fixture() +async def async_no_receive_function_contract( + async_w3, + AsyncNoReceiveFunctionContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncNoReceiveFunctionContract, address_conversion_func) + + @pytest.fixture() def ReceiveFunctionContract(w3, RECEIVE_FUNCTION_CONTRACT): return w3.eth.contract(**RECEIVE_FUNCTION_CONTRACT) +@pytest.fixture() +def AsyncReceiveFunctionContract(async_w3, RECEIVE_FUNCTION_CONTRACT): + return async_w3.eth.contract(**RECEIVE_FUNCTION_CONTRACT) + + +@pytest.fixture() +def receive_function_contract(w3, ReceiveFunctionContract, address_conversion_func): + return deploy(w3, ReceiveFunctionContract, address_conversion_func) + + +@pytest_asyncio.fixture() +async def async_receive_function_contract( + async_w3, + AsyncReceiveFunctionContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncReceiveFunctionContract, address_conversion_func) + + CONTRACT_CALLER_TESTER_SOURCE = """ contract CallerTester { int public count; @@ -919,6 +1224,11 @@ def CallerTesterContract(w3, CALLER_TESTER_CONTRACT): return w3.eth.contract(**CALLER_TESTER_CONTRACT) +@pytest.fixture() +def AsyncCallerTesterContract(async_w3, CALLER_TESTER_CONTRACT): + return async_w3.eth.contract(**CALLER_TESTER_CONTRACT) + + @pytest.fixture() def REVERT_CONTRACT_CODE(): return REVERT_CONTRACT_BYTECODE @@ -950,6 +1260,21 @@ def RevertContract(w3, REVERT_FUNCTION_CONTRACT): return w3.eth.contract(**REVERT_FUNCTION_CONTRACT) +@pytest.fixture() +def revert_contract(w3, RevertContract, address_conversion_func): + return deploy(w3, RevertContract, address_conversion_func) + + +@pytest.fixture() +def AsyncRevertContract(async_w3, REVERT_FUNCTION_CONTRACT): + return async_w3.eth.contract(**REVERT_FUNCTION_CONTRACT) + + +@pytest_asyncio.fixture() +async def async_revert_contract(async_w3, AsyncRevertContract, address_conversion_func): + return await async_deploy(async_w3, AsyncRevertContract, address_conversion_func) + + class LogFunctions: LogAnonymous = 0 LogNoArguments = 1 @@ -1025,47 +1350,58 @@ def invoke_contract(api_call_desig='call', return result +async def async_invoke_contract( + api_call_desig='call', + contract=None, + contract_function=None, + func_args=[], + func_kwargs={}, + tx_params={}): + 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}") + + function = contract.functions[contract_function] + result = await getattr(function(*func_args, **func_kwargs), api_call_desig)(tx_params) + + return result + + @pytest.fixture def transact(request): return functools.partial(invoke_contract, api_call_desig='transact') +@pytest.fixture() +def async_transact(request): + return async_partial(async_invoke_contract, api_call_desig='transact') + + @pytest.fixture def call(request): return functools.partial(invoke_contract, api_call_desig='call') +@pytest.fixture() +def async_call(request): + return async_partial(async_invoke_contract, api_call_desig='call') + + @pytest.fixture def estimate_gas(request): return functools.partial(invoke_contract, api_call_desig='estimate_gas') +@pytest.fixture() +def async_estimate_gas(request): + return async_partial(async_invoke_contract, api_call_desig='estimate_gas') + + @pytest.fixture 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): - args = args or [] - deploy_txn = await Contract.constructor(*args).transact() - 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 async_web3.eth.get_code(contract.address)) > 0 - return contract - - -@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) +@pytest.fixture() +def async_build_transaction(request): + return async_partial(async_invoke_contract, api_call_desig='build_transaction') diff --git a/tests/core/contracts/test_concise_contract.py b/tests/core/contracts/test_concise_contract.py index c54e4932f1..fb1c25e950 100644 --- a/tests/core/contracts/test_concise_contract.py +++ b/tests/core/contracts/test_concise_contract.py @@ -7,6 +7,9 @@ decode_hex, ) +from utils import ( + deploy, +) from web3.contract import ( CONCISE_NORMALIZERS, ConciseContract, @@ -14,16 +17,6 @@ ) -def deploy(w3, Contract, args=None): - args = args or [] - deploy_txn = Contract.constructor(*args).transact() - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - assert deploy_receipt is not None - contract = Contract(address=deploy_receipt['contractAddress']) - assert len(w3.eth.get_code(contract.address)) > 0 - return contract - - @pytest.fixture() def EMPTY_ADDR(address_conversion_func): addr = '0x' + '00' * 20 diff --git a/tests/core/contracts/test_contract_buildTransaction.py b/tests/core/contracts/test_contract_buildTransaction.py index 3f1b6a3723..252b0c78e3 100644 --- a/tests/core/contracts/test_contract_buildTransaction.py +++ b/tests/core/contracts/test_contract_buildTransaction.py @@ -9,39 +9,6 @@ ) -@pytest.fixture() -def math_contract(w3, MathContract, address_conversion_func): - deploy_txn = MathContract.constructor().transact() - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - assert deploy_receipt is not None - math_contract_address = address_conversion_func(deploy_receipt['contractAddress']) - _math_contract = MathContract(address=math_contract_address) - assert _math_contract.address == math_contract_address - return _math_contract - - -@pytest.fixture() -def fallback_function_contract(w3, FallbackFunctionContract, address_conversion_func): - deploy_txn = FallbackFunctionContract.constructor().transact() - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - assert deploy_receipt is not None - fallback_contract_address = address_conversion_func(deploy_receipt['contractAddress']) - _fallback_contract = FallbackFunctionContract(address=fallback_contract_address) - assert _fallback_contract.address == fallback_contract_address - return _fallback_contract - - -@pytest.fixture() -def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): - deploy_txn = PayableTesterContract.constructor().transact() - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - assert deploy_receipt is not None - payable_tester_address = address_conversion_func(deploy_receipt['contractAddress']) - _payable_tester = PayableTesterContract(address=payable_tester_address) - assert _payable_tester.address == payable_tester_address - return _payable_tester - - def test_build_transaction_not_paying_to_nonpayable_function( w3, payable_tester_contract, @@ -235,3 +202,219 @@ def test_build_transaction_with_contract_with_arguments(w3, skip_if_testrpc, mat else: assert 'gas' in txn assert dissoc(txn, 'gas') == expected + + +@pytest.mark.asyncio +async def test_async_build_transaction_not_paying_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_build_transaction): + txn = await async_build_transaction(contract=async_payable_tester_contract, + contract_function='doNoValueCall') + assert dissoc(txn, 'gas') == { + 'to': async_payable_tester_contract.address, + 'data': '0xe4cb8f5c', + 'value': 0, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_paying_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_build_transaction): + with pytest.raises(ValidationError): + await async_build_transaction(contract=async_payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_no_arguments( + async_w3, + async_math_contract, + async_build_transaction): + txn = await async_build_transaction(contract=async_math_contract, contract_function='increment') + assert dissoc(txn, 'gas') == { + 'to': async_math_contract.address, + 'data': '0xd09de08a', + 'value': 0, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_fallback_function( + async_w3, + async_fallback_function_contract): + txn = await async_fallback_function_contract.fallback.build_transaction() + assert dissoc(txn, 'gas') == { + 'to': async_fallback_function_contract.address, + 'data': '0x', + 'value': 0, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_class_method( + async_w3, + AsyncMathContract, + async_math_contract, + async_build_transaction): + txn = await async_build_transaction( + contract=AsyncMathContract, + contract_function='increment', + tx_params={'to': async_math_contract.address}, + ) + assert dissoc(txn, 'gas') == { + 'to': async_math_contract.address, + 'data': '0xd09de08a', + 'value': 0, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_default_account_is_set( + async_w3, + async_math_contract, + async_build_transaction): + txn = await async_build_transaction(contract=async_math_contract, contract_function='increment') + assert dissoc(txn, 'gas') == { + 'to': async_math_contract.address, + 'data': '0xd09de08a', + 'value': 0, + 'maxFeePerGas': 2750000000, + 'maxPriorityFeePerGas': 10 ** 9, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_gas_price_strategy_set( + async_w3, + async_math_contract, + async_build_transaction): + def my_gas_price_strategy(async_w3, transaction_params): + return 5 + async_w3.eth.set_gas_price_strategy(my_gas_price_strategy) + txn = await async_build_transaction(contract=async_math_contract, contract_function='increment') + assert dissoc(txn, 'gas') == { + 'to': async_math_contract.address, + 'data': '0xd09de08a', + 'value': 0, + 'gasPrice': 5, + 'chainId': 61, + } + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_data_supplied_errors( + async_w3, + async_math_contract, + async_build_transaction): + with pytest.raises(ValueError): + await async_build_transaction(contract=async_math_contract, + contract_function='increment', + tx_params={'data': '0x000'}) + + +@pytest.mark.asyncio +async def test_async_build_transaction_with_contract_to_address_supplied_errors( + async_w3, + async_math_contract, + async_build_transaction): + with pytest.raises(ValueError): + await async_build_transaction(contract=async_math_contract, + contract_function='increment', + tx_params={'to': '0xb2930B35844a230f00E51431aCAe96Fe543a0347'}) # noqa: E501 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'transaction_args,method_args,method_kwargs,expected,skip_testrpc', + ( + ( + {}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, + }, False + ), + ( + {'gas': 800000}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, + }, False + ), + ( # legacy transaction, explicit gasPrice + {'gasPrice': 22 ** 8}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 61, + }, False + ), + ( + {'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8, + 'chainId': 61, + }, False + ), + ( + {'nonce': 7}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'nonce': 7, 'chainId': 61, + }, True + ), + ( + {'value': 20000}, (5,), {}, { + 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 + 'value': 20000, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, + 'chainId': 61, + }, False + ), + ), + ids=[ + 'Standard', + 'Explicit Gas', + 'Explicit Gas Price', + 'Explicit Dynamic Fees', + 'Explicit Nonce', + 'With Value', + ] +) +async def test_async_build_transaction_with_contract_with_arguments( + async_w3, async_skip_if_testrpc, async_math_contract, + transaction_args, + method_args, + method_kwargs, + expected, + skip_testrpc, + async_build_transaction): + if skip_testrpc: + async_skip_if_testrpc(async_w3) + + txn = await async_build_transaction(contract=async_math_contract, + contract_function='increment', + func_args=method_args, + func_kwargs=method_kwargs, + tx_params=transaction_args) + expected['to'] = async_math_contract.address + assert txn is not None + if 'gas' in transaction_args: + assert txn['gas'] == transaction_args['gas'] + else: + assert 'gas' in txn + assert dissoc(txn, 'gas') == expected diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 1b87b48f7d..2d1258ac68 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -15,13 +15,15 @@ from eth_utils import ( is_text, ) -from eth_utils.toolz import ( - identity, -) from hexbytes import ( HexBytes, ) +import pytest_asyncio +from utils import ( + async_deploy, + deploy, +) from web3._utils.ens import ( contract_ens_addresses, ) @@ -37,70 +39,6 @@ ) -def deploy(w3, Contract, apply_func=identity, args=None): - args = args or [] - deploy_txn = Contract.constructor(*args).transact() - deploy_receipt = w3.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(w3.eth.get_code(contract.address)) > 0 - return contract - - -@pytest.fixture() -def address_reflector_contract(w3, AddressReflectorContract, address_conversion_func): - return deploy(w3, AddressReflectorContract, address_conversion_func) - - -@pytest.fixture() -def math_contract(w3, MathContract, address_conversion_func): - return deploy(w3, MathContract, address_conversion_func) - - -@pytest.fixture() -def string_contract(w3, StringContract, address_conversion_func): - return deploy(w3, StringContract, address_conversion_func, args=["Caqalai"]) - - -@pytest.fixture() -def arrays_contract(w3, ArraysContract, address_conversion_func): - # bytes_32 = [keccak('0'), keccak('1')] - bytes32_array = [ - b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 - b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 - ] - byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] - return deploy(w3, ArraysContract, address_conversion_func, args=[bytes32_array, byte_arr]) - - -@pytest.fixture() -def strict_arrays_contract(w3_strict_abi, StrictArraysContract, address_conversion_func): - # bytes_32 = [keccak('0'), keccak('1')] - bytes32_array = [ - b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 - b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 - ] - byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] - return deploy( - w3_strict_abi, - StrictArraysContract, - address_conversion_func, - args=[bytes32_array, byte_arr] - ) - - -@pytest.fixture() -def address_contract(w3, WithConstructorAddressArgumentsContract, address_conversion_func): - return deploy( - w3, - WithConstructorAddressArgumentsContract, - address_conversion_func, - args=["0xd3CdA913deB6f67967B99D67aCDFa1712C293601"] - ) - - @pytest.fixture(params=[b'\x04\x06', '0x0406', '0406']) def bytes_contract(w3, BytesContract, request, address_conversion_func): if is_text(request.param) and request.param[:2] != '0x': @@ -113,19 +51,39 @@ def bytes_contract(w3, BytesContract, request, address_conversion_func): return deploy(w3, BytesContract, address_conversion_func, args=[request.param]) -@pytest.fixture() -def fixed_reflection_contract(w3, FixedReflectionContract, address_conversion_func): - return deploy(w3, FixedReflectionContract, address_conversion_func) +@pytest_asyncio.fixture(params=[b'\x04\x06', '0x0406', '0406']) +async def async_bytes_contract( + async_w3, + AsyncBytesContract, + request, + address_conversion_func): + if is_text(request.param) and request.param[:2] != '0x': + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + return await async_deploy(async_w3, + AsyncBytesContract, + address_conversion_func, + args=[request.param]) + else: + return await async_deploy(async_w3, + AsyncBytesContract, + address_conversion_func, + args=[request.param]) @pytest.fixture() -def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): - return deploy(w3, PayableTesterContract, address_conversion_func) +def fixed_reflection_contract(w3, FixedReflectionContract, address_conversion_func): + return deploy(w3, FixedReflectionContract, address_conversion_func) -@pytest.fixture() -def revert_contract(w3, RevertContract, address_conversion_func): - return deploy(w3, RevertContract, address_conversion_func) +@pytest_asyncio.fixture() +async def async_fixed_reflection_contract( + async_w3, + AsyncFixedReflectionContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncFixedReflectionContract, address_conversion_func) @pytest.fixture() @@ -149,6 +107,25 @@ def bytes32_contract(w3, Bytes32Contract, request, address_conversion_func): return deploy(w3, Bytes32Contract, address_conversion_func, args=[request.param]) +@pytest_asyncio.fixture(params=[ + '0x0406040604060406040604060406040604060406040604060406040604060406', + '0406040604060406040604060406040604060406040604060406040604060406', + HexBytes('0406040604060406040604060406040604060406040604060406040604060406'), +]) +async def async_bytes32_contract(async_w3, AsyncBytes32Contract, request, address_conversion_func): + if is_text(request.param) and request.param[:2] != '0x': + with pytest.warns(DeprecationWarning): + return await async_deploy(async_w3, + AsyncBytes32Contract, + address_conversion_func, + args=[request.param]) + else: + return await async_deploy(async_w3, + AsyncBytes32Contract, + address_conversion_func, + args=[request.param]) + + @pytest.fixture() def undeployed_math_contract(MathContract, address_conversion_func): empty_address = address_conversion_func("0x000000000000000000000000000000000000dEaD") @@ -156,6 +133,13 @@ def undeployed_math_contract(MathContract, address_conversion_func): return _undeployed_math_contract +@pytest_asyncio.fixture() +async def async_undeployed_math_contract(AsyncMathContract, address_conversion_func): + empty_address = address_conversion_func("0x000000000000000000000000000000000000dEaD") + _undeployed_math_contract = AsyncMathContract(address=empty_address) + return _undeployed_math_contract + + @pytest.fixture() def mismatched_math_contract(w3, StringContract, MathContract, address_conversion_func): deploy_txn = StringContract.constructor("Caqalai").transact() @@ -166,19 +150,18 @@ def mismatched_math_contract(w3, StringContract, MathContract, address_conversio return _mismatched_math_contract -@pytest.fixture() -def fallback_function_contract(w3, FallbackFunctionContract, address_conversion_func): - return deploy(w3, FallbackFunctionContract, address_conversion_func) - - -@pytest.fixture() -def receive_function_contract(w3, ReceiveFunctionContract, address_conversion_func): - return deploy(w3, ReceiveFunctionContract, address_conversion_func) - - -@pytest.fixture() -def no_receive_function_contract(w3, NoReceiveFunctionContract, address_conversion_func): - return deploy(w3, NoReceiveFunctionContract, address_conversion_func) +@pytest_asyncio.fixture() +async def async_mismatched_math_contract( + async_w3, + AsyncStringContract, + AsyncMathContract, + address_conversion_func): + deploy_txn = await AsyncStringContract.constructor("Caqalai").transact() + deploy_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert deploy_receipt is not None + address = address_conversion_func(deploy_receipt['contractAddress']) + _mismatched_math_contract = AsyncMathContract(address=address) + return _mismatched_math_contract @pytest.fixture() @@ -186,11 +169,21 @@ def tuple_contract(w3, TupleContract, address_conversion_func): return deploy(w3, TupleContract, address_conversion_func) +@pytest_asyncio.fixture() +async def async_tuple_contract(async_w3, AsyncTupleContract, address_conversion_func): + return await async_deploy(async_w3, AsyncTupleContract, address_conversion_func) + + @pytest.fixture() def nested_tuple_contract(w3, NestedTupleContract, address_conversion_func): return deploy(w3, NestedTupleContract, address_conversion_func) +@pytest_asyncio.fixture() +async def async_nested_tuple_contract(async_w3, AsyncNestedTupleContract, address_conversion_func): + return await async_deploy(async_w3, AsyncNestedTupleContract, address_conversion_func) + + def test_invalid_address_in_deploy_arg(WithConstructorAddressArgumentsContract): with pytest.raises(InvalidAddress): WithConstructorAddressArgumentsContract.constructor( @@ -881,6 +874,775 @@ def test_call_revert_contract(revert_contract): revert_contract.functions.revertWithMessage().call({'gas': 100000}) +@pytest.mark.asyncio +async def test_async_invalid_address_in_deploy_arg(AsyncWithConstructorAddressArgumentsContract): + with pytest.raises(InvalidAddress): + await AsyncWithConstructorAddressArgumentsContract.constructor( + "0xd3cda913deb6f67967b99d67acdfa1712c293601", + ).transact() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'call_args,call_kwargs', + ( + ((9, 7), {}), + ((9,), {'b': 7}), + (tuple(), {'a': 9, 'b': 7}), + ), +) +async def test_async_call_with_multiple_arguments( + async_math_contract, + async_call, + call_args, + call_kwargs): + result = await async_call(contract=async_math_contract, + contract_function='add', + func_args=call_args, + func_kwargs=call_kwargs) + assert result == 16 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'call_args,call_kwargs', + ( + ((9, 7), {}), + ((9,), {'b': 7}), + (tuple(), {'a': 9, 'b': 7}), + ), +) +async def test_async_saved_method_call_with_multiple_arguments( + async_math_contract, + call_args, + call_kwargs): + math_contract_add = async_math_contract.functions.add(*call_args, **call_kwargs) + result = await math_contract_add.call() + assert result == 16 + + +@pytest.mark.asyncio +async def test_async_call_get_string_value(async_string_contract, async_call): + result = await async_call(contract=async_string_contract, + contract_function='getValue') + # eth_abi.decode_abi() does not assume implicit utf-8 + # encoding of string return values. Thus, we need to decode + # ourselves for fair comparison. + assert result == "Caqalai" + + +@pytest.mark.asyncio +@pytest.mark.skipif( + LooseVersion(eth_abi.__version__) >= LooseVersion("2"), + reason="eth-abi >=2 does utf-8 string decoding") +async def test_async_call_read_string_variable(async_string_contract, async_call): + result = await async_call(contract=async_string_contract, + contract_function='constValue') + assert result == b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff".decode(errors='backslashreplace') # noqa: E501 + + +@pytest.mark.asyncio +@pytest.mark.skipif( + LooseVersion(eth_abi.__version__) < LooseVersion("2"), + reason="eth-abi does not raise exception on undecodable bytestrings") +async def test_async_call_on_undecodable_string(async_string_contract, async_call): + with pytest.raises(BadFunctionCallOutput): + await async_call( + contract=async_string_contract, + contract_function='constValue') + + +@pytest.mark.asyncio +async def test_async_call_get_bytes32_array(async_arrays_contract, async_call): + result = await async_call(contract=async_arrays_contract, + contract_function='getBytes32Value') + # expected_bytes32_array = [keccak('0'), keccak('1')] + expected_bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + assert result == expected_bytes32_array + + +@pytest.mark.asyncio +async def test_async_call_get_bytes32_const_array(async_arrays_contract, async_call): + result = await async_call(contract=async_arrays_contract, + contract_function='getBytes32ConstValue') + # expected_bytes32_array = [keccak('A'), keccak('B')] + expected_bytes32_array = [ + b'\x03x?\xac.\xfe\xd8\xfb\xc9\xadD>Y.\xe3\x0ea\xd6_G\x11@\xc1\x0c\xa1U\xe97\xb45\xb7`', + b'\x1fg[\xff\x07Q_]\xf9g7\x19N\xa9E\xc3lA\xe7\xb4\xfc\xef0{|\xd4\xd0\xe6\x02\xa6\x91\x11', + ] + assert result == expected_bytes32_array + + +@pytest.mark.asyncio +async def test_async_call_get_byte_array(async_arrays_contract, async_call): + result = await async_call(contract=async_arrays_contract, + contract_function='getByteValue') + expected_byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + assert result == expected_byte_arr + + +@pytest.mark.asyncio +@pytest.mark.parametrize('args,expected', [([b''], [b'\x00']), (['0x'], [b'\x00'])]) +async def test_async_set_byte_array( + async_arrays_contract, + async_call, + async_transact, + args, + expected): + await async_transact( + contract=async_arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + result = await async_call(contract=async_arrays_contract, + contract_function='getByteValue') + + assert result == expected + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'args,expected', [ + ([b'1'], [b'1']), + (['0xDe'], [b'\xDe']) + ] +) +async def test_async_set_strict_byte_array( + async_strict_arrays_contract, + async_call, + async_transact, + args, + expected): + await async_transact( + contract=async_strict_arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + result = await async_call(contract=async_strict_arrays_contract, + contract_function='getByteValue') + + assert result == expected + + +@pytest.mark.asyncio +@pytest.mark.parametrize('args', ([''], ['s'])) +async def test_async_set_strict_byte_array_with_invalid_args( + async_strict_arrays_contract, + async_transact, + args): + with pytest.raises(ValidationError): + await async_transact( + contract=async_strict_arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + + +@pytest.mark.asyncio +async def test_async_call_get_byte_const_array(async_arrays_contract, async_call): + result = await async_call(contract=async_arrays_contract, + contract_function='getByteConstValue') + expected_byte_arr = [b'\x00', b'\x01'] + assert result == expected_byte_arr + + +@pytest.mark.asyncio +async def test_async_call_read_address_variable(async_address_contract, async_call): + result = await async_call(contract=async_address_contract, + contract_function='testAddr') + assert result == "0xd3CdA913deB6f67967B99D67aCDFa1712C293601" + + +# TODO reenable once ENS is async +# @pytest.mark.asyncio +# async def test_async_init_with_ens_name_arg( +# async_w3, +# AsyncWithConstructorAddressArgumentsContract, +# async_call): +# with contract_ens_addresses( +# AsyncWithConstructorAddressArgumentsContract, +# [("arg-name.eth", "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")], +# ): +# address_contract = await async_deploy(async_w3, +# AsyncWithConstructorAddressArgumentsContract, +# args=["arg-name.eth",]) + +# result = await async_call(contract=address_contract, +# contract_function='testAddr') +# assert result == "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413" + + +@pytest.mark.asyncio +async def test_async_call_read_bytes_variable(async_bytes_contract, async_call): + result = await async_call(contract=async_bytes_contract, contract_function='constValue') + assert result == b"\x01\x23" + + +@pytest.mark.asyncio +async def test_async_call_get_bytes_value(async_bytes_contract, async_call): + result = await async_call(contract=async_bytes_contract, contract_function='getValue') + assert result == b'\x04\x06' + + +@pytest.mark.asyncio +async def test_async_call_read_bytes32_variable(async_bytes32_contract, async_call): + result = await async_call(contract=async_bytes32_contract, contract_function='constValue') + assert result == b"\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23\x01\x23" # noqa + + +@pytest.mark.asyncio +async def test_async_call_get_bytes32_value(async_bytes32_contract, async_call): + result = await async_call(contract=async_bytes32_contract, contract_function='getValue') + assert result == b'\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06\x04\x06' # noqa + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'value, expected', + [ + ( + '0x' + '11' * 20, + '0x' + '11' * 20, + ), + ( + '0xbb9bc244d798123fde783fcc1c72d3bb8c189413', + InvalidAddress, + ), + ( + '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', + '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', + ), + ] +) +async def test_async_call_address_reflector_with_address( + async_address_reflector_contract, + value, + expected, + async_call): + if not isinstance(expected, str): + with pytest.raises(expected): + await async_call(contract=async_address_reflector_contract, + contract_function='reflect', + func_args=[value]) + else: + assert await async_call(contract=async_address_reflector_contract, + contract_function='reflect', + func_args=[value]) == expected + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'value, expected', + [ + ( + ['0x' + '11' * 20, '0x' + '22' * 20], + ['0x' + '11' * 20, '0x' + '22' * 20], + ), + ( + ['0x' + '11' * 20, '0x' + 'aa' * 20], + InvalidAddress + ), + ( + [ + '0xFeC2079e80465cc8C687fFF9EE6386ca447aFec4', + '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', + ], + [ + '0xFeC2079e80465cc8C687fFF9EE6386ca447aFec4', + '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', + ], + ), + ] +) +async def test_async_call_address_list_reflector_with_address( + async_address_reflector_contract, + value, + expected, + async_call): + if not isinstance(expected, list): + with pytest.raises(expected): + await async_call(contract=async_address_reflector_contract, + contract_function='reflect', + func_args=[value]) + else: + assert await async_call(contract=async_address_reflector_contract, + contract_function='reflect', + func_args=[value]) == expected + + +# TODO reenable after ens is asynced +# @pytest.mark.asyncio +# async def test_async_call_address_reflector_single_name( +# async_address_reflector_contract, +# async_call): +# with contract_ens_addresses( +# async_address_reflector_contract, +# [("dennisthepeasant.eth", "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")], +# ): +# result = await async_call(contract=async_address_reflector_contract, +# contract_function='reflect', +# func_args=['dennisthepeasant.eth']) +# assert result == '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413' + + +# TODO reenable once ENS is asynced +# @pytest.mark.asyncio +# async def test_async_call_address_reflector_name_array( +# async_address_reflector_contract, +# async_call): +# names = [ +# 'autonomouscollective.eth', +# 'wedonthavealord.eth', +# ] +# addresses = [ +# '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', +# '0xFeC2079e80465cc8C687fFF9EE6386ca447aFec4', +# ] + +# with contract_ens_addresses(async_address_reflector_contract, zip(names, addresses)): +# result = await async_call(contract=async_address_reflector_contract, +# contract_function='reflect', +# func_args=[names]) + +# assert addresses == result + + +# TODO reenable once ENS is asynced +# @pytest.mark.asyncio +# async def test_async_call_reject_invalid_ens_name( +# async_address_reflector_contract, +# async_call): +# with contract_ens_addresses(async_address_reflector_contract, []): +# with pytest.raises(ValueError): +# await async_call(contract=async_address_reflector_contract, +# contract_function='reflect', +# func_args=['type0.eth']) + + +@pytest.mark.asyncio +async def test_async_call_missing_function(async_mismatched_math_contract, async_call): + expected_missing_function_error_message = "Could not decode contract function call" + with pytest.raises(BadFunctionCallOutput) as exception_info: + await async_call(contract=async_mismatched_math_contract, contract_function='return13') + assert expected_missing_function_error_message in str(exception_info.value) + + +# TODO bug in use of contract.py, _contract_call_function +# @pytest.mark.asyncio +# async def test_async_call_undeployed_contract(async_undeployed_math_contract, async_call): +# expected_undeployed_call_error_message = "Could not transact with/call contract function" +# with pytest.raises(BadFunctionCallOutput) as exception_info: +# await async_call(contract=async_undeployed_math_contract, contract_function='return13') +# assert expected_undeployed_call_error_message in str(exception_info.value) + + +@pytest.mark.asyncio +async def test_async_call_fallback_function(async_fallback_function_contract): + result = await async_fallback_function_contract.fallback.call() + assert result == [] + + +@pytest.mark.asyncio +@pytest.mark.parametrize('tx_params,contract_name,expected', ( + ({'gas': 210000}, 'no_receive', 'fallback'), + ({'gas': 210000, 'value': 2}, 'no_receive', ''), + ({'value': 2, 'gas': 210000, 'data': '0x477a5c98'}, 'no_receive', ''), + ({'gas': 210000, 'data': '0x477a5c98'}, 'no_receive', 'fallback'), + ({'data': '0x477a5c98'}, 'receive', 'fallback'), + ({'value': 2}, 'receive', 'receive'), +)) +async def test_async_call_receive_fallback_function( + async_w3, + tx_params, + expected, + async_call, + async_receive_function_contract, + async_no_receive_function_contract, + contract_name): + if contract_name == 'receive': + contract = async_receive_function_contract + elif contract_name == 'no_receive': + contract = async_no_receive_function_contract + else: + raise AssertionError('contract must be either receive or no_receive') + + initial_value = await async_call(contract=contract, contract_function='getText') + assert initial_value == '' + to = {'to': contract.address} + merged = {**to, **tx_params} + await async_w3.eth.send_transaction(merged) + final_value = await async_call(contract=contract, contract_function='getText') + assert final_value == expected + + +@pytest.mark.asyncio +async def test_async_call_nonexistent_receive_function(async_fallback_function_contract): + with pytest.raises(FallbackNotFound, match='No receive function was found'): + await async_fallback_function_contract.receive.call() + + +@pytest.mark.asyncio +async def test_async_throws_error_if_block_out_of_range(async_w3, async_math_contract): + await async_w3.provider.make_request(method='evm_mine', params=[20]) + with pytest.raises(BlockNumberOutofRange): + await async_math_contract.functions.counter().call(block_identifier=-50) + + +@pytest.mark.asyncio +async def test_async_accepts_latest_block(async_w3, async_math_contract): + await async_w3.provider.make_request(method='evm_mine', params=[5]) + await async_math_contract.functions.increment().transact() + + late = await async_math_contract.functions.counter().call(block_identifier='latest') + pend = await async_math_contract.functions.counter().call(block_identifier='pending') + + assert late == 1 + assert pend == 1 + + +@pytest.mark.asyncio +async def test_async_accepts_block_hash_as_identifier(async_w3, async_math_contract): + blocks = await async_w3.provider.make_request(method='evm_mine', params=[5]) + await async_math_contract.functions.increment().transact() + more_blocks = await async_w3.provider.make_request(method='evm_mine', params=[5]) + + old = await async_math_contract.functions.counter().call(block_identifier=blocks['result'][2]) + new = await async_math_contract.functions.counter().call(block_identifier=more_blocks['result'][2]) # noqa: E501 + + assert old == 0 + assert new == 1 + + +@pytest.mark.asyncio +async def test_async_neg_block_indexes_from_the_end( + async_w3, + async_math_contract): + 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() + await async_w3.provider.make_request(method='evm_mine', params=[5]) + + output1 = await async_math_contract.functions.counter().call(block_identifier=-7) + output2 = await async_math_contract.functions.counter().call(block_identifier=-6) + + assert output1 == 1 + assert output2 == 2 + +message_regex = ( + r"\nCould not identify the intended function with name `.*`, " + r"positional argument\(s\) of type `.*` and " + r"keyword argument\(s\) of type `.*`." + r"\nFound .* function\(s\) with the name `.*`: .*" +) +diagnosis_arg_regex = ( + r"\nFunction invocation failed due to improper number of arguments." +) +diagnosis_encoding_regex = ( + r"\nFunction invocation failed due to no matching argument types." +) +diagnosis_ambiguous_encoding = ( + r"\nAmbiguous argument encoding. " + r"Provided arguments can be encoded to multiple functions matching this call." +) + + +@pytest.mark.asyncio +async def test_async_no_functions_match_identifier(async_arrays_contract): + with pytest.raises(MismatchedABI): + await async_arrays_contract.functions.thisFunctionDoesNotExist().call() + + +@pytest.mark.asyncio +async def test_async_function_1_match_identifier_wrong_number_of_args( + async_arrays_contract): + regex = message_regex + diagnosis_arg_regex + with pytest.raises(ValidationError, match=regex): + await async_arrays_contract.functions.setBytes32Value().call() + + +@pytest.mark.asyncio +async def test_async_function_1_match_identifier_wrong_args_encoding( + async_arrays_contract): + regex = message_regex + diagnosis_encoding_regex + with pytest.raises(ValidationError, match=regex): + await async_arrays_contract.functions.setBytes32Value('dog').call() + + +@pytest.mark.asyncio +async def test_async_function_multiple_match_identifiers_no_correct_number_of_args(async_w3): + MULTIPLE_FUNCTIONS = json.loads('[{"constant":false,"inputs":[],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint256"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint8"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"int8"}],"name":"a","outputs":[],"type":"function"}]') # noqa: E501 + Contract = async_w3.eth.contract(abi=MULTIPLE_FUNCTIONS) + regex = message_regex + diagnosis_arg_regex + with pytest.raises(ValidationError, match=regex): + await Contract.functions.a(100, 'dog').call() + + +@pytest.mark.asyncio +async def test_async_function_multiple_match_identifiers_no_correct_encoding_of_args(async_w3): + MULTIPLE_FUNCTIONS = json.loads('[{"constant":false,"inputs":[],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint256"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint8"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"int8"}],"name":"a","outputs":[],"type":"function"}]') # noqa: E501 + Contract = async_w3.eth.contract(abi=MULTIPLE_FUNCTIONS) + regex = message_regex + diagnosis_encoding_regex + with pytest.raises(ValidationError, match=regex): + await Contract.functions.a('dog').call() + + +@pytest.mark.asyncio +async def test_async_function_multiple_possible_encodings(async_w3): + MULTIPLE_FUNCTIONS = json.loads('[{"constant":false,"inputs":[],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint256"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint8"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"int8"}],"name":"a","outputs":[],"type":"function"}]') # noqa: E501 + Contract = async_w3.eth.contract(abi=MULTIPLE_FUNCTIONS) + regex = message_regex + diagnosis_ambiguous_encoding + with pytest.raises(ValidationError, match=regex): + await Contract.functions.a(100).call() + + +@pytest.mark.asyncio +async def test_async_function_no_abi(async_w3): + contract = async_w3.eth.contract() + with pytest.raises(NoABIFound): + await contract.functions.thisFunctionDoesNotExist().call() + + +@pytest.mark.asyncio +async def test_async_call_abi_no_functions(async_w3): + contract = async_w3.eth.contract(abi=[]) + with pytest.raises(NoABIFunctionsFound): + await contract.functions.thisFunctionDoesNotExist().call() + + +@pytest.mark.asyncio +async def test_async_call_not_sending_ether_to_nonpayable_function( + async_payable_tester_contract, + async_call): + result = await async_call(contract=async_payable_tester_contract, + contract_function='doNoValueCall') + assert result == [] + + +@pytest.mark.asyncio +async def test_async_call_sending_ether_to_nonpayable_function( + async_payable_tester_contract, + async_call): + with pytest.raises(ValidationError): + await async_call(contract=async_payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'function, value', + ( + # minimum positive unambiguous value (larger than fixed8x1) + ('reflect', Decimal('12.8')), + # maximum value (for ufixed256x1) + ('reflect', Decimal(2 ** 256 - 1) / 10), + # maximum negative unambiguous value (less than 0 from ufixed*) + ('reflect', Decimal('-0.1')), + # minimum value (for fixed8x1) + ('reflect', Decimal('-12.8')), + # only ufixed256x80 type supports 2-80 decimals + ('reflect', Decimal(2 ** 256 - 1) / 10 ** 80), # maximum allowed value + ('reflect', Decimal(1) / 10 ** 80), # smallest non-zero value + # minimum value (for ufixed8x1) + ('reflect_short_u', 0), + # maximum value (for ufixed8x1) + ('reflect_short_u', Decimal('25.5')), + ), +) +async def test_async_reflect_fixed_value( + async_fixed_reflection_contract, + function, + value): + contract_func = async_fixed_reflection_contract.functions[function] + reflected = await contract_func(value).call({'gas': 420000}) + assert reflected == value + + +DEFAULT_DECIMALS = getcontext().prec + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'function, value, error', + ( + # out of range + ('reflect_short_u', Decimal('25.6'), "no matching argument types"), + ('reflect_short_u', Decimal('-.1'), "no matching argument types"), + # too many digits for *x1, too large for 256x80 + ('reflect', Decimal('0.01'), "no matching argument types"), + + # too many digits + ('reflect_short_u', Decimal('0.01'), "no matching argument types"), + ( + 'reflect_short_u', + Decimal(f'1e-{DEFAULT_DECIMALS + 1}'), + "no matching argument types", + ), + ('reflect_short_u', Decimal('25.4' + '9' * DEFAULT_DECIMALS), "no matching argument types"), + ('reflect', Decimal(1) / 10 ** 81, "no matching argument types"), + + # floats not accepted, for floating point error concerns + ('reflect_short_u', 0.1, "no matching argument types"), + + # ambiguous + ('reflect', Decimal('12.7'), "Ambiguous argument encoding"), + ('reflect', Decimal(0), "Ambiguous argument encoding"), + ('reflect', 0, "Ambiguous argument encoding"), + ), +) +async def test_async_invalid_fixed_value_reflections( + async_fixed_reflection_contract, + function, + value, + error): + contract_func = async_fixed_reflection_contract.functions[function] + with pytest.raises(ValidationError, match=error): + await contract_func(value).call({'gas': 420000}) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'method_input, expected', + ( + ( + {'a': 123, 'b': [1, 2], 'c': [ + {'x': 234, 'y': [True, False], 'z': [ + '0x4AD7E79d88650B01EEA2B1f069f01EE9db343d5c', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + ]}, + {'x': 345, 'y': [False, False], 'z': [ + '0xefd1FF70c185A1C0b125939815225199079096Ee', + '0xf35C0784794F3Cd935F5754d3a0EbcE95bEf851e', + ]}, + ]}, + (123, [1, 2], [ + (234, [True, False], [ + '0x4AD7E79d88650B01EEA2B1f069f01EE9db343d5c', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + ]), + (345, [False, False], [ + '0xefd1FF70c185A1C0b125939815225199079096Ee', + '0xf35C0784794F3Cd935F5754d3a0EbcE95bEf851e', + ]), + ]), + ), + ( + (123, [1, 2], [ + (234, [True, False], [ + '0x4AD7E79d88650B01EEA2B1f069f01EE9db343d5c', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + ]), + (345, [False, False], [ + '0xefd1FF70c185A1C0b125939815225199079096Ee', + '0xf35C0784794F3Cd935F5754d3a0EbcE95bEf851e', + ]), + ]), + (123, [1, 2], [ + (234, [True, False], [ + '0x4AD7E79d88650B01EEA2B1f069f01EE9db343d5c', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + '0xfdF1946A9b40245224488F1a36f4A9ed4844a523', + ]), + (345, [False, False], [ + '0xefd1FF70c185A1C0b125939815225199079096Ee', + '0xf35C0784794F3Cd935F5754d3a0EbcE95bEf851e', + ]), + ]), + ), + ), +) +async def test_async_call_tuple_contract(async_tuple_contract, method_input, expected): + result = await async_tuple_contract.functions.method(method_input).call() + assert result == expected + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'method_input, expected', + ( + ( + {'t': [ + {'u': [ + {'x': 1, 'y': 2}, + {'x': 3, 'y': 4}, + {'x': 5, 'y': 6}, + ]}, + {'u': [ + {'x': 7, 'y': 8}, + {'x': 9, 'y': 10}, + {'x': 11, 'y': 12}, + ]}, + ]}, + ( + [ + ([ + (1, 2), + (3, 4), + (5, 6), + ],), + ([ + (7, 8), + (9, 10), + (11, 12), + ],), + ], + ), + ), + ( + ( + [ + ([ + (1, 2), + (3, 4), + (5, 6), + ],), + ([ + (7, 8), + (9, 10), + (11, 12), + ],), + ], + ), + ( + [ + ([ + (1, 2), + (3, 4), + (5, 6), + ],), + ([ + (7, 8), + (9, 10), + (11, 12), + ],), + ], + ), + ), + ), +) +async def test_async_call_nested_tuple_contract( + async_nested_tuple_contract, + method_input, + expected): + result = await async_nested_tuple_contract.functions.method(method_input).call() + assert result == expected + + +@pytest.mark.asyncio +async def test_async_call_revert_contract(async_revert_contract): + with pytest.raises(TransactionFailed, match="Function has been reverted."): + # eth-tester will do a gas estimation if we don't submit a gas value, + # which does not contain the revert reason. Avoid that by giving a gas + # value. + await async_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() diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 9553766115..69ef3bd71a 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -1,9 +1,11 @@ import pytest -from eth_utils.toolz import ( - identity, -) +import pytest_asyncio +from utils import ( + async_deploy, + deploy, +) from web3.exceptions import ( MismatchedABI, NoABIFound, @@ -11,33 +13,24 @@ ) -def deploy(w3, Contract, apply_func=identity, args=None): - args = args or [] - deploy_txn = Contract.constructor(*args).transact() - deploy_receipt = w3.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(w3.eth.get_code(contract.address)) > 0 - return contract - - @pytest.fixture() def address(w3): return w3.eth.accounts[1] -@pytest.fixture() -def math_contract(w3, MathContract, address_conversion_func): - return deploy(w3, MathContract, address_conversion_func) - - @pytest.fixture() def caller_tester_contract(w3, CallerTesterContract, address_conversion_func): return deploy(w3, CallerTesterContract, address_conversion_func) +@pytest_asyncio.fixture() +async def async_caller_tester_contract( + async_w3, + AsyncCallerTesterContract, + address_conversion_func): + return await async_deploy(async_w3, AsyncCallerTesterContract, address_conversion_func) + + @pytest.fixture() def transaction_dict(w3, address): return { @@ -180,3 +173,156 @@ def test_caller_with_args_and_no_transaction_keyword(w3, add_result = contract.add(3, 5) assert add_result == 8 + + +@pytest.mark.asyncio +async def async_test_caller_default(async_math_contract): + result = await async_math_contract.caller.add(3, 5) + assert result == 8 + + +@pytest.mark.asyncio +async def test_async_caller_with_parens(async_math_contract): + result = await async_math_contract.caller().add(3, 5) + assert result == 8 + + +@pytest.mark.asyncio +async def test_async_caller_with_no_abi(async_w3): + contract = async_w3.eth.contract() + with pytest.raises(NoABIFound): + await contract.caller.thisFunctionDoesNotExist() + + +@pytest.mark.asyncio +async def test_async_caller_with_no_abi_and_parens(async_w3): + contract = async_w3.eth.contract() + with pytest.raises(NoABIFound): + await contract.caller().thisFunctionDoesNotExist() + + +@pytest.mark.asyncio +async def test_async_caller_with_empty_abi_and_parens(async_w3): + contract = async_w3.eth.contract(abi=[]) + with pytest.raises(NoABIFunctionsFound): + await contract.caller().thisFunctionDoesNotExist() + + +@pytest.mark.asyncio +async def test_async_caller_with_empty_abi(async_w3): + contract = async_w3.eth.contract(abi=[]) + with pytest.raises(NoABIFunctionsFound): + await contract.caller.thisFunctionDoesNotExist() + + +@pytest.mark.asyncio +async def test_async_caller_raw_getattr_with_missing_element(async_math_contract): + with pytest.raises(MismatchedABI, match="not found in this contract's ABI"): + await async_math_contract.caller.__getattr__('notafunction') + + +@pytest.mark.asyncio +async def test_async_caller_raw_getattr_with_present_element(async_math_contract): + attr = async_math_contract.caller.__getattr__('return13') + assert attr + + +@pytest.mark.asyncio +async def test_async_caller_with_a_nonexistent_function(async_math_contract): + contract = async_math_contract + with pytest.raises(MismatchedABI, match="not found in this contract's ABI"): + await contract.caller.thisFunctionDoesNotExist() + + +@pytest.mark.asyncio +async def test_async_caller_with_block_identifier(async_w3, async_math_contract): + start = await async_w3.eth.get_block('latest') + start_num = start.number + assert await async_math_contract.caller.counter() == 0 + + 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.caller(block_identifier=start_num + 6).counter() + output2 = await async_math_contract.caller(block_identifier=start_num + 7).counter() + + assert output1 == 1 + assert output2 == 2 + + +@pytest.mark.asyncio +async def test_async_caller_with_block_identifier_and_transaction_dict( + async_w3, + async_caller_tester_contract, + transaction_dict, + address): + start = await async_w3.eth.get_block('latest') + start_num = start.number + assert await async_caller_tester_contract.caller.counter() == 0 + + await async_w3.provider.make_request(method='evm_mine', params=[5]) + await async_caller_tester_contract.functions.increment().transact() + + block_id = start_num + 6 + contract = async_caller_tester_contract.caller( + transaction=transaction_dict, + block_identifier=block_id + ) + + sender, _, gasLeft, value, block_num = await contract.returnMeta() + counter = await contract.counter() + + assert sender == address + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + assert block_num == block_id + assert counter == 1 + + +@pytest.mark.asyncio +async def test_async_caller_with_transaction_keyword( + async_w3, + async_caller_tester_contract, + transaction_dict, + address): + contract = async_caller_tester_contract.caller(transaction=transaction_dict) + + sender, _, gasLeft, value, _ = await contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + + +@pytest.mark.asyncio +async def test_async_caller_with_dict_but_no_transaction_keyword( + async_w3, + async_caller_tester_contract, + transaction_dict, + address): + contract = async_caller_tester_contract.caller(transaction_dict) + + sender, _, gasLeft, value, _ = await contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + + +@pytest.mark.asyncio +async def test_async_caller_with_args_and_no_transaction_keyword( + async_w3, + async_caller_tester_contract, + transaction_dict, + address): + contract = async_caller_tester_contract.caller(transaction_dict) + + sender, _, gasLeft, value, _ = await contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + + add_result = await contract.add(3, 5) + assert add_result == 8 diff --git a/tests/core/contracts/test_contract_constructor.py b/tests/core/contracts/test_contract_constructor.py index 7321ae7ace..1006ec586f 100644 --- a/tests/core/contracts/test_contract_constructor.py +++ b/tests/core/contracts/test_contract_constructor.py @@ -242,3 +242,263 @@ def test_contract_constructor_build_transaction_with_constructor_with_argument( new_txn = w3.eth.get_transaction(new_txn_hash) assert new_txn['data'] == unsent_txn['data'] assert new_txn['nonce'] == nonce + + +def test_async_contract_constructor_abi_encoding_with_no_constructor_fn( + AsyncMathContract, + MATH_CODE): + deploy_data = AsyncMathContract.constructor()._encode_data_in_transaction() + assert deploy_data == MATH_CODE + + +@pytest.mark.asyncio +async def test_async_contract_constructor_gas_estimate_no_constructor( + async_w3, + AsyncMathContract): + gas_estimate = await AsyncMathContract.constructor().estimate_gas() + + deploy_txn = await AsyncMathContract.constructor().transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_constructor_gas_estimate_with_block_id( + async_w3, + AsyncMathContract): + block_identifier = None + gas_estimate = await AsyncMathContract.constructor().estimate_gas( + block_identifier=block_identifier) + deploy_txn = await AsyncMathContract.constructor().transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_constructor_gas_estimate_with_constructor_without_arguments( + async_w3, + AsyncSimpleConstructorContract): + gas_estimate = await AsyncSimpleConstructorContract.constructor().estimate_gas() + + deploy_txn = await AsyncSimpleConstructorContract.constructor().transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'constructor_args,constructor_kwargs', + ( + ([1234, b'abcd'], {}), + ([1234], {'b': b'abcd'}), + ([], {'a': 1234, 'b': b'abcd'}), + ([], {'b': b'abcd', 'a': 1234}), + ), +) +async def test_async_contract_constructor_gas_estimate_with_constructor_with_arguments( + async_w3, + AsyncWithConstructorArgumentsContract, + constructor_args, + constructor_kwargs): + gas_estimate = await AsyncWithConstructorArgumentsContract.constructor( + *constructor_args, **constructor_kwargs).estimate_gas() + + deploy_txn = await AsyncWithConstructorArgumentsContract.constructor( + *constructor_args, **constructor_kwargs).transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_constructor_gas_estimate_with_constructor_with_address_argument( + async_w3, + AsyncWithConstructorAddressArgumentsContract, + address_conversion_func): + gas_estimate = await AsyncWithConstructorAddressArgumentsContract.constructor( + address_conversion_func("0x16D9983245De15E7A9A73bC586E01FF6E08dE737")).estimate_gas() + + deploy_txn = await AsyncWithConstructorAddressArgumentsContract.constructor( + address_conversion_func("0x16D9983245De15E7A9A73bC586E01FF6E08dE737")).transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_constructor_transact_no_constructor( + async_w3, + AsyncMathContract, + MATH_RUNTIME, + address_conversion_func): + deploy_txn = await AsyncMathContract.constructor().transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = address_conversion_func(txn_receipt['contractAddress']) + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(MATH_RUNTIME) + + +@pytest.mark.asyncio +async def test_async_contract_constructor_transact_with_constructor_without_arguments( + async_w3, + AsyncSimpleConstructorContract, + SIMPLE_CONSTRUCTOR_RUNTIME, + address_conversion_func): + deploy_txn = await AsyncSimpleConstructorContract.constructor().transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = address_conversion_func(txn_receipt['contractAddress']) + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(SIMPLE_CONSTRUCTOR_RUNTIME) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'constructor_args,constructor_kwargs, expected_a, expected_b', + ( + ([1234, b'abcd'], {}, EXPECTED_DATA_A, EXPECTED_DATA_B), + ([1234], {'b': b'abcd'}, EXPECTED_DATA_A, EXPECTED_DATA_B), + ([], {'a': 1234, 'b': b'abcd'}, EXPECTED_DATA_A, EXPECTED_DATA_B), + ([], {'b': b'abcd', 'a': 1234}, EXPECTED_DATA_A, EXPECTED_DATA_B), + ), +) +async def test_async_contract_constructor_transact_with_constructor_with_arguments( + async_w3, + AsyncWithConstructorArgumentsContract, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + constructor_args, + constructor_kwargs, + expected_a, + expected_b, + address_conversion_func): + deploy_txn = await AsyncWithConstructorArgumentsContract.constructor( + *constructor_args, **constructor_kwargs).transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = address_conversion_func(txn_receipt['contractAddress']) + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) + assert expected_a == await AsyncWithConstructorArgumentsContract( + address=contract_address).functions.data_a().call() + assert expected_b == await AsyncWithConstructorArgumentsContract( + address=contract_address).functions.data_b().call() + + +@pytest.mark.asyncio +async def test_async_contract_constructor_transact_with_constructor_with_address_arguments( + async_w3, + AsyncWithConstructorAddressArgumentsContract, + WITH_CONSTRUCTOR_ADDRESS_RUNTIME, + address_conversion_func): + deploy_txn = await AsyncWithConstructorAddressArgumentsContract.constructor(TEST_ADDRESS).transact() # noqa: E501 + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + assert txn_receipt['contractAddress'] + contract_address = address_conversion_func(txn_receipt['contractAddress']) + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ADDRESS_RUNTIME) + assert TEST_ADDRESS == await AsyncWithConstructorAddressArgumentsContract( + address=contract_address).functions.testAddr().call() + + +@pytest.mark.asyncio +async def test_async_contract_constructor_build_transaction_to_field_error(AsyncMathContract): + with pytest.raises(ValueError): + await AsyncMathContract.constructor().build_transaction({'to': '123'}) + + +# @pytest.mark.asyncio +# async def test_async_contract_constructor_build_transaction_no_constructor( +# async_w3, +# AsyncMathContract, +# address_conversion_func): +# async_w3_accounts = await async_w3.eth.accounts +# txn_hash = await AsyncMathContract.constructor().transact( +# {'from': address_conversion_func(async_w3_accounts[0])} +# ) +# txn = await async_w3.eth.get_transaction(txn_hash) +# async_w3_coinbase = await async_w3.eth.coinbase +# nonce = await async_w3.eth.get_transaction_count(async_w3_coinbase) +# unsent_txn = await AsyncMathContract.constructor().build_transaction({'nonce': nonce}) +# assert txn['data'] == unsent_txn['data'] + +# new_txn_hash = await async_w3.eth.send_transaction(unsent_txn) +# new_txn = await async_w3.eth.get_transaction(new_txn_hash) +# assert new_txn['data'] == unsent_txn['data'] +# assert new_txn['nonce'] == nonce + + +# @pytest.mark.asyncio +# async def test_async_contract_constructor_build_transaction_with_constructor_without_argument( +# async_w3, +# AsyncMathContract, +# address_conversion_func): +# async_w3_accounts = await async_w3.eth.accounts +# txn_hash = await AsyncMathContract.constructor().transact( +# {'from': address_conversion_func(async_w3_accounts[0])} +# ) +# txn = await async_w3.eth.get_transaction(txn_hash) +# async_w3_coinbase = await async_w3.eth.coinbase +# nonce = await async_w3.eth.get_transaction_count(async_w3_coinbase) +# unsent_txn = await AsyncMathContract.constructor().build_transaction({'nonce': nonce}) +# assert txn['data'] == unsent_txn['data'] + +# new_txn_hash = await async_w3.eth.send_transaction(unsent_txn) +# new_txn = await async_w3.eth.get_transaction(new_txn_hash) +# assert new_txn['data'] == unsent_txn['data'] +# assert new_txn['nonce'] == nonce + + +# @pytest.mark.asyncio +# @pytest.mark.parametrize( +# 'constructor_args,constructor_kwargs', +# ( +# ([1234, b'abcd'], {}), +# ([1234], {'b': b'abcd'}), +# ([], {'a': 1234, 'b': b'abcd'}), +# ([], {'b': b'abcd', 'a': 1234}), +# ), +# ) +# async def test_async_contract_constructor_build_transaction_with_constructor_with_argument( +# async_w3, +# AsyncWithConstructorArgumentsContract, +# constructor_args, +# constructor_kwargs, +# address_conversion_func): +# async_w3_accounts = await async_w3.eth.accounts +# txn_hash = await AsyncWithConstructorArgumentsContract.constructor( +# *constructor_args, **constructor_kwargs).transact( +# {'from': address_conversion_func(async_w3_accounts[0])} +# ) +# txn = await async_w3.eth.get_transaction(txn_hash) +# async_w3_coinbase = await async_w3.eth.coinbase +# nonce = await async_w3.eth.get_transaction_count(async_w3_coinbase) +# unsent_txn = await AsyncWithConstructorArgumentsContract.constructor( +# *constructor_args, **constructor_kwargs).build_transaction({'nonce': nonce}) +# assert txn['data'] == unsent_txn['data'] + +# new_txn_hash = await async_w3.eth.send_transaction(unsent_txn) +# new_txn = await async_w3.eth.get_transaction(new_txn_hash) +# assert new_txn['data'] == unsent_txn['data'] +# assert new_txn['nonce'] == nonce diff --git a/tests/core/contracts/test_contract_deployment.py b/tests/core/contracts/test_contract_deployment.py index f0b2e80d81..8a2b7d3d37 100644 --- a/tests/core/contracts/test_contract_deployment.py +++ b/tests/core/contracts/test_contract_deployment.py @@ -105,3 +105,112 @@ def test_contract_deployment_with_constructor_with_address_argument(w3, blockchain_code = w3.eth.get_code(contract_address) assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ADDRESS_RUNTIME) + + +@pytest.mark.asyncio +async def test_async_contract_deployment_no_constructor(async_w3, AsyncMathContract, + MATH_RUNTIME): + deploy_txn = await AsyncMathContract.constructor().transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(MATH_RUNTIME) + + +@pytest.mark.asyncio +async def test_async_contract_deployment_with_constructor_without_args( + async_w3, + AsyncSimpleConstructorContract, + SIMPLE_CONSTRUCTOR_RUNTIME): + deploy_txn = await AsyncSimpleConstructorContract.constructor().transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(SIMPLE_CONSTRUCTOR_RUNTIME) + + +@pytest.mark.asyncio +async def test_async_contract_deployment_with_constructor_with_arguments( + async_w3, + AsyncWithConstructorArgumentsContract, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME): + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + deploy_txn = await AsyncWithConstructorArgumentsContract.constructor(1234, 'abcd').transact() # noqa: E501 + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) + + +@pytest.mark.asyncio +@pytest.mark.parametrize('constructor_arg', ( + b'1234\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', # noqa: E501 + constants.HASH_ZERO) +) +async def test_async_contract_deployment_with_constructor_with_arguments_strict( + async_w3_strict_abi, + AsyncWithConstructorArgumentsContractStrict, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + constructor_arg): + + deploy_txn = await AsyncWithConstructorArgumentsContractStrict.constructor( + 1234, constructor_arg + ).transact() + + txn_receipt = await async_w3_strict_abi.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = await async_w3_strict_abi.eth.get_code(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) + + +@pytest.mark.asyncio +async def test_async_contract_deployment_with_constructor_with_arguments_strict_error( + async_w3_strict_abi, + AsyncWithConstructorArgumentsContractStrict, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME): + with pytest.raises( + TypeError, + match="One or more arguments could not be encoded to the necessary ABI type. Expected types are: uint256, bytes32" # noqa: E501 + ): + await AsyncWithConstructorArgumentsContractStrict.constructor(1234, 'abcd').transact() + + +@pytest.mark.asyncio +async def test_async_contract_deployment_with_constructor_with_address_argument( + async_w3, + AsyncWithConstructorAddressArgumentsContract, # noqa: E501 + WITH_CONSTRUCTOR_ADDRESS_RUNTIME): # noqa: E501 + deploy_txn = await AsyncWithConstructorAddressArgumentsContract.constructor( + "0x16D9983245De15E7A9A73bC586E01FF6E08dE737", + ).transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = await async_w3.eth.get_code(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ADDRESS_RUNTIME) diff --git a/tests/core/contracts/test_contract_estimateGas.py b/tests/core/contracts/test_contract_estimateGas.py index b704cb54cb..217ec39b49 100644 --- a/tests/core/contracts/test_contract_estimateGas.py +++ b/tests/core/contracts/test_contract_estimateGas.py @@ -5,72 +5,6 @@ ) -@pytest.fixture(autouse=True) -def wait_for_first_block(w3, wait_for_block): - wait_for_block(w3) - - -@pytest.fixture() -def math_contract(w3, - MATH_ABI, - MATH_CODE, - MATH_RUNTIME, - wait_for_transaction, - address_conversion_func): - MathContract = w3.eth.contract( - abi=MATH_ABI, - bytecode=MATH_CODE, - bytecode_runtime=MATH_RUNTIME, - ) - deploy_txn = MathContract.constructor().transact({'from': w3.eth.coinbase}) - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - - assert deploy_receipt is not None - contract_address = address_conversion_func(deploy_receipt['contractAddress']) - w3.isAddress(contract_address) - - _math_contract = MathContract(address=contract_address) - assert _math_contract.address == contract_address - return _math_contract - - -@pytest.fixture() -def fallback_function_contract(w3, - FALLBACK_FUNCTION_ABI, - FALLBACK_FUNCTION_CODE, - FALLBACK_FUNCTION_RUNTIME, - wait_for_transaction, - address_conversion_func): - fallback_contract = w3.eth.contract( - abi=FALLBACK_FUNCTION_ABI, - bytecode=FALLBACK_FUNCTION_CODE, - bytecode_runtime=FALLBACK_FUNCTION_RUNTIME - ) - deploy_txn = fallback_contract.constructor().transact({'from': w3.eth.coinbase}) - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - - assert deploy_receipt is not None - contract_address = address_conversion_func(deploy_receipt['contractAddress']) - w3.isAddress(contract_address) - - _fallback_function_contract = fallback_contract(address=contract_address) - assert _fallback_function_contract.address == contract_address - return _fallback_function_contract - - -@pytest.fixture() -def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): - deploy_txn = PayableTesterContract.constructor().transact({'from': w3.eth.coinbase}) - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - - assert deploy_receipt is not None - payable_tester_address = address_conversion_func(deploy_receipt['contractAddress']) - - _payable_tester = PayableTesterContract(address=payable_tester_address) - assert _payable_tester.address == payable_tester_address - return _payable_tester - - def test_contract_estimate_gas(w3, math_contract, estimate_gas, transact): gas_estimate = estimate_gas(contract=math_contract, contract_function='increment') @@ -164,3 +98,122 @@ def test_estimate_gas_block_identifier_unique_estimates(w3, math_contract, trans ) assert latest_gas_estimate != earliest_gas_estimate + + +@pytest.mark.asyncio +async def test_async_contract_estimate_gas( + async_w3, + async_math_contract, + async_estimate_gas, + async_transact): + gas_estimate = await async_estimate_gas(contract=async_math_contract, + contract_function='increment') + + txn_hash = await async_transact( + contract=async_math_contract, + contract_function='increment') + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_fallback_estimate_gas( + async_w3, + async_fallback_function_contract): + gas_estimate = await async_fallback_function_contract.fallback.estimate_gas() + + txn_hash = await async_fallback_function_contract.fallback.transact() + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_contract_estimate_gas_with_arguments( + async_w3, + async_math_contract, + async_estimate_gas, + async_transact): + gas_estimate = await async_estimate_gas(contract=async_math_contract, + contract_function='add', + func_args=[5, 6]) + + txn_hash = await async_transact( + contract=async_math_contract, + contract_function='add', + func_args=[5, 6]) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_estimate_gas_not_sending_ether_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_estimate_gas, + async_transact): + gas_estimate = await async_estimate_gas(contract=async_payable_tester_contract, + contract_function='doNoValueCall') + + txn_hash = await async_transact( + contract=async_payable_tester_contract, + contract_function='doNoValueCall') + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_estimate_gas_sending_ether_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_estimate_gas): + with pytest.raises(ValidationError): + await async_estimate_gas(contract=async_payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) + + +@pytest.mark.asyncio +async def test_async_estimate_gas_accepts_latest_block( + async_w3, + async_math_contract, + async_transact): + gas_estimate = await async_math_contract.functions.counter().estimate_gas( + block_identifier='latest') + + txn_hash = await async_transact( + contract=async_math_contract, + contract_function='increment') + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + gas_used = txn_receipt.get('gasUsed') + + assert abs(gas_estimate - gas_used) < 21000 + + +@pytest.mark.asyncio +async def test_async_estimate_gas_block_identifier_unique_estimates( + async_w3, + async_math_contract, + async_transact): + txn_hash = await async_transact(contract=async_math_contract, contract_function="increment") + await async_w3.eth.wait_for_transaction_receipt(txn_hash) + + latest_gas_estimate = await async_math_contract.functions.counter().estimate_gas( + block_identifier="latest" + ) + earliest_gas_estimate = await async_math_contract.functions.counter().estimate_gas( + block_identifier="earliest" + ) + + assert latest_gas_estimate != earliest_gas_estimate diff --git a/tests/core/contracts/test_contract_example.py b/tests/core/contracts/test_contract_example.py index 60e1b05cf6..ab8318e373 100644 --- a/tests/core/contracts/test_contract_example.py +++ b/tests/core/contracts/test_contract_example.py @@ -1,10 +1,18 @@ # This file is used by the documentation as an example of how to write unit tests with web3.py import pytest +import pytest_asyncio + from web3 import ( EthereumTesterProvider, Web3, ) +from web3.eth import ( + AsyncEth, +) +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) @pytest.fixture @@ -101,3 +109,104 @@ def test_updating_greeting_emits_event(w3, foo_contract): event = logs[0] assert event.blockHash == receipt.blockHash assert event.args._bar == "testing contracts is easy" + + +@pytest.fixture +def async_eth_tester(): + return AsyncEthereumTesterProvider().ethereum_tester + + +@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() +async def async_foo_contract(async_w3): + # For simplicity of this example we statically define the + # contract code here. You might read your contracts from a + # file, or something else to test with in your own code + # + # pragma solidity^0.5.3; + # + # contract Foo { + # + # string public bar; + # event barred(string _bar); + # + # constructor() public { + # bar = "hello world"; + # } + # + # function setBar(string memory _bar) public { + # bar = _bar; + # emit barred(_bar); + # } + # + # } + + async_eth_tester_accounts = await async_w3.eth.accounts + deploy_address = async_eth_tester_accounts[0] + + abi = """[{"anonymous":false,"inputs":[{"indexed":false,"name":"_bar","type":"string"}],"name":"barred","type":"event"},{"constant":false,"inputs":[{"name":"_bar","type":"string"}],"name":"setBar","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":true,"inputs":[],"name":"bar","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]""" # noqa: E501 + # This bytecode is the output of compiling with + # solc version:0.5.3+commit.10d17f24.Emscripten.clang + bytecode = """608060405234801561001057600080fd5b506040805190810160405280600b81526020017f68656c6c6f20776f726c640000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6103bb806101166000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806397bc14aa14610058578063febb0f7e14610113575b600080fd5b6101116004803603602081101561006e57600080fd5b810190808035906020019064010000000081111561008b57600080fd5b82018360208201111561009d57600080fd5b803590602001918460018302840111640100000000831117156100bf57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610196565b005b61011b61024c565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561015b578082015181840152602081019050610140565b50505050905090810190601f1680156101885780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101ac9291906102ea565b507f5f71ad82e16f082de5ff496b140e2fbc8621eeb37b36d59b185c3f1364bbd529816040518080602001828103825283818151815260200191508051906020019080838360005b8381101561020f5780820151818401526020810190506101f4565b50505050905090810190601f16801561023c5780820380516001836020036101000a031916815260200191505b509250505060405180910390a150565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102e25780601f106102b7576101008083540402835291602001916102e2565b820191906000526020600020905b8154815290600101906020018083116102c557829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061032b57805160ff1916838001178555610359565b82800160010185558215610359579182015b8281111561035857825182559160200191906001019061033d565b5b509050610366919061036a565b5090565b61038c91905b80821115610388576000816000905550600101610370565b5090565b9056fea165627a7a72305820ae6ca683d45ee8a71bba45caee29e4815147cd308f772c853a20dfe08214dbb50029""" # noqa: E501 + + # Create our contract class. + FooContract = async_w3.eth.contract(abi=abi, bytecode=bytecode) + # issue a transaction to deploy the contract. + tx_hash = await FooContract.constructor().transact({ + 'from': deploy_address, + }) + # wait for the transaction to be mined + tx_receipt = await async_w3.eth.wait_for_transaction_receipt(tx_hash, 180) + # instantiate and return an instance of our contract. + return FooContract(tx_receipt.contractAddress) + + +@pytest.mark.asyncio +async def test_async_initial_greeting(async_foo_contract): + hw = await async_foo_contract.caller.bar() + assert hw == "hello world" + + +@pytest.mark.asyncio +async def test_async_can_update_greeting(async_w3, async_foo_contract): + async_eth_tester_accounts = await async_w3.eth.accounts + # send transaction that updates the greeting + tx_hash = await async_foo_contract.functions.setBar( + "testing contracts is easy", + ).transact({ + 'from': async_eth_tester_accounts[1], + }) + await async_w3.eth.wait_for_transaction_receipt(tx_hash, 180) + + # verify that the contract is now using the updated greeting + hw = await async_foo_contract.caller.bar() + assert hw == "testing contracts is easy" + + +@pytest.mark.asyncio +async def test_async_updating_greeting_emits_event(async_w3, async_foo_contract): + async_eth_tester_accounts = await async_w3.eth.accounts + # send transaction that updates the greeting + tx_hash = await async_foo_contract.functions.setBar( + "testing contracts is easy", + ).transact({ + 'from': async_eth_tester_accounts[1], + }) + receipt = await async_w3.eth.wait_for_transaction_receipt(tx_hash, 180) + + # get all of the `barred` logs for the contract + logs = await async_foo_contract.events.barred.getLogs() + assert len(logs) == 1 + + # verify that the log's data matches the expected value + event = logs[0] + assert event.blockHash == receipt.blockHash + assert event.args._bar == "testing contracts is easy" diff --git a/tests/core/contracts/test_contract_transact_interface.py b/tests/core/contracts/test_contract_transact_interface.py index 05e88bc0d8..b44a018938 100644 --- a/tests/core/contracts/test_contract_transact_interface.py +++ b/tests/core/contracts/test_contract_transact_interface.py @@ -3,9 +3,6 @@ from eth_utils import ( to_bytes, ) -from eth_utils.toolz import ( - identity, -) from web3._utils.empty import ( empty, @@ -15,54 +12,6 @@ ) -def deploy(w3, Contract, apply_func=identity, args=None): - args = args or [] - deploy_txn = Contract.constructor(*args).transact() - deploy_receipt = w3.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(w3.eth.get_code(contract.address)) > 0 - return contract - - -@pytest.fixture() -def math_contract(w3, MathContract, address_conversion_func): - return deploy(w3, MathContract, address_conversion_func) - - -@pytest.fixture() -def string_contract(w3, StringContract, address_conversion_func): - return deploy(w3, StringContract, address_conversion_func, args=["Caqalai"]) - - -@pytest.fixture() -def fallback_function_contract(w3, FallbackFunctionContract, address_conversion_func): - return deploy(w3, FallbackFunctionContract, address_conversion_func) - - -@pytest.fixture() -def arrays_contract(w3, ArraysContract, address_conversion_func): - # bytes_32 = [keccak('0'), keccak('1')] - bytes32_array = [ - b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 - b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 - ] - byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] - return deploy(w3, ArraysContract, address_conversion_func, args=[bytes32_array, byte_arr]) - - -@pytest.fixture() -def payable_tester_contract(w3, PayableTesterContract, address_conversion_func): - return deploy(w3, PayableTesterContract, address_conversion_func) - - -@pytest.fixture() -def receive_function_contract(w3, ReceiveFunctionContract, address_conversion_func): - return deploy(w3, ReceiveFunctionContract, address_conversion_func) - - def test_transacting_with_contract_no_arguments(w3, math_contract, transact, call): initial_value = call(contract=math_contract, contract_function='counter') @@ -327,3 +276,308 @@ def test_receive_contract_with_fallback_function(receive_function_contract, call final_value = call(contract=receive_function_contract, contract_function='getText') assert final_value == 'receive' + + +@pytest.mark.asyncio +async def test_async_transacting_with_contract_no_arguments( + async_w3, + async_math_contract, + async_transact, + async_call): + initial_value = await async_call(contract=async_math_contract, + contract_function='counter') + + txn_hash = await async_transact(contract=async_math_contract, + contract_function='increment') + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_math_contract, + contract_function='counter') + + assert final_value - initial_value == 1 + + +@pytest.mark.asyncio +async def test_async_transact_not_sending_ether_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_transact, + async_call): + initial_value = await async_call(contract=async_payable_tester_contract, + contract_function='wasCalled') + + assert initial_value is False + txn_hash = await async_transact(contract=async_payable_tester_contract, + contract_function='doNoValueCall') + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_payable_tester_contract, + contract_function='wasCalled') + + assert final_value is True + + +@pytest.mark.asyncio +async def test_async_transact_sending_ether_to_nonpayable_function( + async_w3, + async_payable_tester_contract, + async_transact, + async_call): + initial_value = await async_call(contract=async_payable_tester_contract, + contract_function='wasCalled') + + assert initial_value is False + with pytest.raises(ValidationError): + txn_hash = await async_transact(contract=async_payable_tester_contract, + contract_function='doNoValueCall', + tx_params={'value': 1}) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_payable_tester_contract, + contract_function='wasCalled') + + assert final_value is False + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'transact_args,transact_kwargs', + ( + ((5,), {}), + (tuple(), {'amt': 5}), + ), +) +async def test_async_transacting_with_contract_with_arguments( + async_w3, + async_math_contract, + async_transact, + async_call, + transact_args, + transact_kwargs): + initial_value = await async_call(contract=async_math_contract, + contract_function='counter') + + txn_hash = await async_transact(contract=async_math_contract, + contract_function='increment', + func_args=transact_args, + func_kwargs=transact_kwargs) + + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_math_contract, + contract_function='counter') + + assert final_value - initial_value == 5 + + +@pytest.mark.asyncio +async def test_async_deploy_when_default_account_is_set( + async_w3, + wait_for_transaction, + STRING_CONTRACT): + async_w3_accounts = await async_w3.eth.accounts + async_w3.eth.default_account = async_w3_accounts[1] + assert async_w3.eth.default_account is not empty + + StringContract = async_w3.eth.contract(**STRING_CONTRACT) + + deploy_txn = await StringContract.constructor("Caqalai").transact() + await async_w3.eth.wait_for_transaction_receipt(deploy_txn) + txn_after = await async_w3.eth.get_transaction(deploy_txn) + assert txn_after['from'] == async_w3.eth.default_account + + +@pytest.mark.asyncio +async def test_async_transact_when_default_account_is_set( + async_w3, + async_wait_for_transaction, + async_math_contract, + async_transact): + async_w3_accounts = await async_w3.eth.accounts + async_w3.eth.default_account = async_w3_accounts[1] + assert async_w3.eth.default_account is not empty + + txn_hash = await async_transact(contract=async_math_contract, + contract_function='increment') + await async_wait_for_transaction(async_w3, txn_hash) + txn_after = await async_w3.eth.get_transaction(txn_hash) + assert txn_after['from'] == async_w3.eth.default_account + + +@pytest.mark.asyncio +async def test_async_transacting_with_contract_with_string_argument( + async_w3, + async_string_contract, + async_transact, + async_call): + # eth_abi will pass as raw bytes, no encoding + # unless we encode ourselves + txn_hash = await async_transact(contract=async_string_contract, + contract_function='setValue', + func_args=["ÄLÄMÖLÖ".encode('utf8')]) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_string_contract, + contract_function='getValue') + + assert final_value == "ÄLÄMÖLÖ" + + +@pytest.mark.asyncio +async def test_async_transacting_with_contract_with_bytes32_array_argument( + async_w3, + async_arrays_contract, + async_transact, + async_call): + # new_bytes32_array = [keccak('1'), keccak('2'), keccak('3')] + new_bytes32_array = [ + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + b'\xad|[\xef\x02x\x16\xa8\x00\xda\x176DO\xb5\x8a\x80~\xf4\xc9`;xHg?~:h\xeb\x14\xa5', + b"*\x80\xe1\xef\x1dxB\xf2\x7f.k\xe0\x97+\xb7\x08\xb9\xa15\xc3\x88`\xdb\xe7<'\xc3Hl4\xf4\xde", # noqa: E501 + ] + txn_hash = await async_transact(contract=async_arrays_contract, + contract_function="setBytes32Value", + func_args=[new_bytes32_array]) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_arrays_contract, + contract_function="getBytes32Value") + assert final_value == new_bytes32_array + + +@pytest.mark.asyncio +async def test_async_transacting_with_contract_with_byte_array_argument( + async_w3, + async_arrays_contract, + async_transact, + async_call): + new_byte_array = [b'\x03', b'\x03', b'\x03', b'\x03', b'\x03', b'\x03'] + txn_hash = await async_transact(contract=async_arrays_contract, + contract_function='setByteValue', + func_args=[new_byte_array]) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_arrays_contract, + contract_function='getByteValue') + assert final_value == new_byte_array + + +@pytest.mark.asyncio +async def test_async_transacting_with_contract_respects_explicit_gas( + async_w3, + STRING_CONTRACT, + async_skip_if_testrpc, + async_wait_for_block, + async_call, + async_transact): + await async_skip_if_testrpc(async_w3) + + await async_wait_for_block(async_w3) + + StringContract = async_w3.eth.contract(**STRING_CONTRACT) + + deploy_txn = StringContract.constructor("Caqalai").transact() + deploy_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn, 30) + assert deploy_receipt is not None + string_contract = StringContract(address=deploy_receipt['contractAddress']) + + # eth_abi will pass as raw bytes, no encoding + # unless we encode ourselves + txn_hash = await async_transact(contract=string_contract, + contract_function='setValue', + func_args=[to_bytes(text="ÄLÄMÖLÖ")], + tx_kwargs={'gas': 200000}) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash, 30) + assert txn_receipt is not None + + final_value = await async_call(contract=string_contract, + contract_function='getValue') + assert to_bytes(text=final_value) == to_bytes(text="ÄLÄMÖLÖ") + + txn = await async_w3.eth.get_transaction(txn_hash) + assert txn['gas'] == 200000 + + +@pytest.mark.asyncio +async def test_async_auto_gas_computation_when_transacting( + async_w3, + STRING_CONTRACT, + async_skip_if_testrpc, + async_wait_for_block, + async_call, + async_transact): + await async_skip_if_testrpc(async_w3) + + await async_wait_for_block(async_w3) + + StringContract = async_w3.eth.contract(**STRING_CONTRACT) + + deploy_txn = await StringContract.constructor("Caqalai").transact() + deploy_receipt = await async_w3.eth.wait_for_transaction_receipt(deploy_txn, 30) + assert deploy_receipt is not None + string_contract = StringContract(address=deploy_receipt['contractAddress']) + + gas_estimate = await 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 + txn_hash = await async_transact(contract=string_contract, + contract_function="setValue", + func_args=[to_bytes(text="ÄLÄMÖLÖ")]) + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash, 30) + assert txn_receipt is not None + + final_value = await async_call(contract=string_contract, + contract_function='getValue') + assert to_bytes(text=final_value) == to_bytes(text="ÄLÄMÖLÖ") + + txn = await async_w3.eth.get_transaction(txn_hash) + assert txn['gas'] == gas_estimate + 100000 + + +@pytest.mark.asyncio +async def test_async_fallback_transacting_with_contract( + async_w3, + async_fallback_function_contract, + async_call): + initial_value = await async_call(contract=async_fallback_function_contract, + contract_function='getData') + txn_hash = await async_fallback_function_contract.fallback.transact() + txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash) + assert txn_receipt is not None + + final_value = await async_call(contract=async_fallback_function_contract, + contract_function='getData') + + assert final_value - initial_value == 1 + + +@pytest.mark.asyncio +async def test_async_receive_function(async_receive_function_contract, async_call): + initial_value = await async_call(contract=async_receive_function_contract, + contract_function='getText') + assert initial_value == '' + await async_receive_function_contract.receive.transact() + final_value = await async_call(contract=async_receive_function_contract, + contract_function='getText') + assert final_value == 'receive' + + +@pytest.mark.asyncio +async def test_async_receive_contract_with_fallback_function( + async_receive_function_contract, + async_call): + initial_value = await async_call(contract=async_receive_function_contract, + contract_function='getText') + assert initial_value == '' + await async_receive_function_contract.fallback.transact() + final_value = await async_call(contract=async_receive_function_contract, + contract_function='getText') + assert final_value == 'receive' diff --git a/tests/core/contracts/test_contract_util_functions.py b/tests/core/contracts/test_contract_util_functions.py index af252824cb..a93c6ad374 100644 --- a/tests/core/contracts/test_contract_util_functions.py +++ b/tests/core/contracts/test_contract_util_functions.py @@ -1,9 +1,99 @@ +import pytest + from web3.contract import ( + async_parse_block_identifier, + async_parse_block_identifier_int, + parse_block_identifier, parse_block_identifier_int, ) +from web3.exceptions import ( + BlockNumberOutofRange, +) + + +@pytest.mark.parametrize( + 'block_identifier,expected_output', + ( + (1, 1), + (-1, 0), + ('latest', 'latest'), + ('earliest', 'earliest'), + ('pending', 'pending'), + ) +) +def test_parse_block_identifier_int_and_string(w3, block_identifier, expected_output): + block_id = parse_block_identifier(w3, block_identifier) + assert block_id == expected_output + + +def test_parse_block_identifier_bytes_and_hex(w3): + block_0 = w3.eth.get_block(0) + block_0_hash = block_0['hash'] + # test retrieve by bytes + block_id_by_hash = parse_block_identifier(w3, block_0_hash) + assert block_id_by_hash == 0 + # test retrieve by hexstring + block_0_hexstring = w3.toHex(block_0_hash) + block_id_by_hex = parse_block_identifier(w3, block_0_hexstring) + assert block_id_by_hex == 0 + + +@pytest.mark.parametrize( + 'block_identifier', + ( + 1.5, 'cats', -70, + ) +) +def test_parse_block_identifier_error(w3, block_identifier): + with pytest.raises(BlockNumberOutofRange): + parse_block_identifier(w3, block_identifier) -# This tests negative block number identifiers, which behave like python +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'block_identifier,expected_output', + ( + (1, 1), + (-1, 0), + ('latest', 'latest'), + ('earliest', 'earliest'), + ('pending', 'pending'), + ) +) +async def test_async_parse_block_identifier_int_and_string( + async_w3, + block_identifier, + expected_output): + block_id = await async_parse_block_identifier(async_w3, block_identifier) + assert block_id == expected_output + + +@pytest.mark.asyncio +async def test_async_parse_block_identifier_bytes_and_hex(async_w3): + block_0 = await async_w3.eth.get_block(0) + block_0_hash = block_0['hash'] + # test retrieve by bytes + block_id_by_hash = await async_parse_block_identifier(async_w3, block_0_hash) + assert block_id_by_hash == 0 + # test retrieve by hexstring + block_0_hexstring = async_w3.toHex(block_0_hash) + block_id_by_hex = await async_parse_block_identifier(async_w3, block_0_hexstring) + assert block_id_by_hex == 0 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'block_identifier', + ( + 1.5, 'cats', -70, + ) +) +async def test_async_parse_block_identifier_error(async_w3, block_identifier): + with pytest.raises(BlockNumberOutofRange): + await async_parse_block_identifier(async_w3, block_identifier) + + +# These test negative block number identifiers, which behave like python # list slices, with -1 being the latest block and -2 being the block before that. # This test is necessary because transaction calls allow negative block indexes, although # get_block() does not allow negative block identifiers. Support for negative block identifier @@ -11,3 +101,10 @@ def test_parse_block_identifier_int(w3): last_num = w3.eth.get_block('latest').number assert 0 == parse_block_identifier_int(w3, -1 - last_num) + + +@pytest.mark.asyncio +async def test_async_parse_block_identifier_int(async_w3): + latest_block = await async_w3.eth.get_block('latest') + last_num = latest_block.number + assert 0 == await async_parse_block_identifier_int(async_w3, -1 - last_num) diff --git a/tests/core/contracts/utils.py b/tests/core/contracts/utils.py new file mode 100644 index 0000000000..e29e36ee92 --- /dev/null +++ b/tests/core/contracts/utils.py @@ -0,0 +1,39 @@ +import asyncio + +from eth_utils.toolz import ( + identity, +) + + +def deploy(w3, Contract, apply_func=identity, args=None): + args = args or [] + deploy_txn = Contract.constructor(*args).transact() + deploy_receipt = w3.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(w3.eth.get_code(contract.address)) > 0 + return contract + + +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 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 async_web3.eth.get_code(contract.address)) > 0 + return contract + + +def async_partial(f, *args, **kwargs): + async def f2(*args2, **kwargs2): + result = f(*args, *args2, **kwargs, **kwargs2) + if asyncio.iscoroutinefunction(f): + result = await result + return result + + return f2 diff --git a/tests/core/providers/test_async_tester_provider.py b/tests/core/providers/test_async_tester_provider.py new file mode 100644 index 0000000000..7e74f13ae4 --- /dev/null +++ b/tests/core/providers/test_async_tester_provider.py @@ -0,0 +1,25 @@ +import pytest + +from web3.providers.eth_tester.main import ( + AsyncEthereumTesterProvider, +) + + +@pytest.mark.asyncio +async def test_async_tester_provider_is_connected() -> None: + provider = AsyncEthereumTesterProvider() + connected = await provider.isConnected() + assert connected + + +@pytest.mark.asyncio +async def test_async_tester_provider_creates_a_block() -> None: + provider = AsyncEthereumTesterProvider() + accounts = await provider.make_request('eth_accounts', []) + a, b = accounts['result'][:2] + current_block = await provider.make_request('eth_blockNumber', []) + assert current_block['result'] == 0 + tx = await provider.make_request('eth_sendTransaction', [{'from': a, 'to': b, 'gas': 21000}]) + assert tx + current_block = await provider.make_request('eth_blockNumber', []) + assert current_block['result'] == 1 diff --git a/tests/core/utilities/test_async_transaction.py b/tests/core/utilities/test_async_transaction.py index 6287fd8665..28e8edfda9 100644 --- a/tests/core/utilities/test_async_transaction.py +++ b/tests/core/utilities/test_async_transaction.py @@ -1,8 +1,54 @@ import pytest +from eth_typing import ( + BlockNumber, +) + from web3._utils.async_transactions import ( fill_transaction_defaults, + get_block_gas_limit, + get_buffered_gas_estimate, +) +from web3._utils.utility_methods import ( + none_in_dict, ) +from web3.constants import ( + DYNAMIC_FEE_TXN_PARAMS, +) + +SIMPLE_CURRENT_TRANSACTION = { + 'blockHash': None, + 'hash': '0x0', + 'nonce': 2, + 'gasPrice': 10, +} + + +@pytest.mark.asyncio() +async def test_get_block_gas_limit_with_block_number(async_w3): + gas_limit = await get_block_gas_limit(async_w3.eth, BlockNumber(0)) + assert isinstance(gas_limit, int) + + +@pytest.mark.asyncio() +async def test_get_block_gas_limit_without_block_number(async_w3): + gas_limit = await get_block_gas_limit(async_w3.eth) + assert isinstance(gas_limit, int) + + +@pytest.mark.asyncio() +async def test_get_buffered_gas_estimate(async_w3): + + txn_params = { + 'data': b'0x1', + } + gas_estimate = await async_w3.eth.estimate_gas(txn_params) + gas_buffer = 100000 + gas_limit = await get_block_gas_limit(async_w3.eth) # type: ignore + + buffered_gas_estimate = await get_buffered_gas_estimate(async_w3, txn_params) + assert isinstance(buffered_gas_estimate, int) + assert buffered_gas_estimate == min(gas_estimate + gas_buffer, gas_limit) @pytest.mark.asyncio() @@ -20,3 +66,13 @@ async def test_fill_transaction_defaults_for_all_params(async_w3): 'maxPriorityFeePerGas': await async_w3.eth.max_priority_fee, 'value': 0, } + + +@pytest.mark.asyncio() +async def test_fill_transaction_defaults_nondynamic_tranaction_fee(async_w3): + gasPrice_transaction = { + 'gasPrice': 10, + } + default_transaction = await fill_transaction_defaults(async_w3, gasPrice_transaction) + + assert none_in_dict(DYNAMIC_FEE_TXN_PARAMS, default_transaction) diff --git a/web3/contract.py b/web3/contract.py index 8822ebd78e..a212e03216 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -2101,7 +2101,8 @@ async def async_parse_block_identifier(w3: 'Web3', elif block_identifier in ['latest', 'earliest', 'pending']: 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 + requested_block = await w3.eth.get_block(block_identifier) # type: ignore + return requested_block['number'] else: raise BlockNumberOutofRange @@ -2122,8 +2123,9 @@ async def async_parse_block_identifier_int(w3: 'Web3', block_identifier_int: int 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 + last_block = await w3.eth.get_block('latest') # type: ignore + last_block_num = last_block.number + block_num = last_block_num + block_identifier_int + 1 if block_num < 0: raise BlockNumberOutofRange return BlockNumber(block_num) # type: ignore @@ -2298,10 +2300,12 @@ async def async_build_transaction_for_function( fn_kwargs=kwargs, ) - prepared_transaction = await async_transactions.fill_transaction_defaults( + # prepared_transaction = await async_transactions.fill_transaction_defaults( + # w3, prepared_transaction) + return await async_transactions.fill_transaction_defaults( w3, prepared_transaction) - return prepared_transaction + # return prepared_transaction def find_functions_by_identifier( From 5b0d99a27890e6912dbf7a2741c5924084e09899 Mon Sep 17 00:00:00 2001 From: pacrob Date: Mon, 25 Apr 2022 18:04:25 +0200 Subject: [PATCH 57/73] add missing awaits --- conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index 802ce8d393..649ac7a09a 100644 --- a/conftest.py +++ b/conftest.py @@ -95,10 +95,10 @@ def _wait_for_block(w3, block_number=1, timeout=None): def async_wait_for_block(): async def _wait_for_block(w3, block_number=1, timeout=None): if not timeout: - timeout = (block_number - w3.eth.block_number) * 3 + timeout = (block_number - await w3.eth.block_number) * 3 poll_delay_counter = PollDelayCounter() with Timeout(timeout) as timeout: - while w3.eth.block_number < block_number: + while await w3.eth.block_number < block_number: w3.manager.request_blocking("evm_mine", []) timeout.sleep(poll_delay_counter()) return _wait_for_block From e13147b6b5f31f9377aa50ff8910529b858c2aba Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 3 May 2022 22:10:34 -0400 Subject: [PATCH 58/73] fixed bug with async_call_contract_function --- web3/contract.py | 120 ++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index a212e03216..305d3ebf47 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1944,19 +1944,39 @@ def check_for_forbidden_api_filter_arguments( "method.") -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: +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, + ) + if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) @@ -1994,7 +2014,7 @@ def _call_contract_function(w3: 'Web3', return normalized_data -def call_contract_function( +async def async_call_contract_function( w3: 'Web3', address: ChecksumAddress, normalizers: Tuple[Callable[..., Any], ...], @@ -2021,65 +2041,47 @@ def call_contract_function( fn_kwargs=kwargs, ) - return_data = w3.eth.call( + 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) + if fn_abi is None: + fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) + output_types = get_abi_output_types(fn_abi) -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, - ) + try: + output_data = w3.codec.decode_abi(output_types, return_data) + except DecodingError as e: + # Provide a more helpful error message than the one provided by + # eth-abi-utils + is_missing_code_error = ( + return_data in ACCEPTABLE_EMPTY_STRINGS + and await w3.eth.get_code(address) in ACCEPTABLE_EMPTY_STRINGS) + if is_missing_code_error: + msg = ( + "Could not transact with/call contract function, is contract " + "deployed correctly and chain synced?" + ) + else: + msg = ( + f"Could not decode contract function call to {function_identifier} with " + f"return data: {str(return_data)}, output_types: {output_types}" + ) + raise BadFunctionCallOutput(msg) from e - return_data = await w3.eth.call( # type: ignore - call_transaction, - block_identifier=block_id, - state_override=state_override, + _normalizers = itertools.chain( + BASE_RETURN_NORMALIZERS, + normalizers, ) + normalized_data = map_abi_data(_normalizers, output_types, output_data) - return _call_contract_function(w3, - address, - normalizers, - function_identifier, - return_data, - contract_abi, - fn_abi, - args, - kwargs) + if len(normalized_data) == 1: + return normalized_data[0] + else: + return normalized_data def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier: From 420897efc37ad1a548560a3fcf02789b1c63c571 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 3 May 2022 22:39:45 -0400 Subject: [PATCH 59/73] lint --- web3/contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web3/contract.py b/web3/contract.py index 305d3ebf47..3a9c4a3a9e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -2059,7 +2059,7 @@ async def async_call_contract_function( # eth-abi-utils is_missing_code_error = ( return_data in ACCEPTABLE_EMPTY_STRINGS - and await w3.eth.get_code(address) in ACCEPTABLE_EMPTY_STRINGS) + and await w3.eth.get_code(address) in ACCEPTABLE_EMPTY_STRINGS) # type: ignore if is_missing_code_error: msg = ( "Could not transact with/call contract function, is contract " From 7ee1037942ccfe8d41aaa19fe55c6870de6fe60d Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 6 May 2022 21:59:24 -0400 Subject: [PATCH 60/73] removed async_fill_default from ethtester middleware --- web3/providers/eth_tester/middleware.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index e230129af4..d6e6e2e485 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -331,17 +331,6 @@ 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]: @@ -372,7 +361,7 @@ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: 'eth_estimateGas', 'eth_sendTransaction', ): - filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) + filled_transaction = fill_default('from', guess_from, web3, params[0]) return await make_request(method, [filled_transaction] + list(params)[1:]) else: From 481c6e6f3b7e3eab76ef4943adead6a03bbcb4ff Mon Sep 17 00:00:00 2001 From: DB Date: Fri, 6 May 2022 22:23:37 -0400 Subject: [PATCH 61/73] added comment to eth_tester.main --- web3/providers/eth_tester/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index ffb20bd2e1..a4daa870df 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -55,6 +55,7 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): ) def __init__(self) -> None: + # do not import eth_tester until runtime, it is not a default dependency from eth_tester import EthereumTester from web3.providers.eth_tester.defaults import API_ENDPOINTS self.ethereum_tester = EthereumTester() From c2355ab9b81ea32c4aca6d319befd3996a769fd5 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 10 May 2022 06:08:09 -0400 Subject: [PATCH 62/73] removed unused comment changed fill_transaction_defaults imports --- web3/contract.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 3a9c4a3a9e..f2f69b5f25 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -49,10 +49,6 @@ HexBytes, ) -from web3._utils import ( - async_transactions, - transactions, -) from web3._utils.abi import ( abi_to_signature, check_if_arguments_can_be_encoded, @@ -67,6 +63,9 @@ merge_args_and_kwargs, receive_func_abi_exists, ) +from web3._utils.async_transactions import ( + fill_transaction_defaults as async_fill_transaction_defaults, +) from web3._utils.blocks import ( is_hex_encoded_block_hash, ) @@ -110,6 +109,9 @@ normalize_address_no_ens, normalize_bytecode, ) +from web3._utils.transactions import ( + fill_transaction_defaults, +) from web3.datastructures import ( AttributeDict, MutableAttributeDict, @@ -866,7 +868,7 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return transactions.fill_transaction_defaults(self.w3, built_transaction) + return fill_transaction_defaults(self.w3, built_transaction) @combomethod def estimate_gas( @@ -893,7 +895,7 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return async_transactions.fill_transaction_defaults(self.w3, built_transaction) + return async_fill_transaction_defaults(self.w3, built_transaction) @combomethod async def estimate_gas( @@ -2272,7 +2274,7 @@ def build_transaction_for_function( fn_kwargs=kwargs, ) - prepared_transaction = transactions.fill_transaction_defaults(w3, prepared_transaction) + prepared_transaction = fill_transaction_defaults(w3, prepared_transaction) return prepared_transaction @@ -2302,13 +2304,9 @@ async def async_build_transaction_for_function( fn_kwargs=kwargs, ) - # prepared_transaction = await async_transactions.fill_transaction_defaults( - # w3, prepared_transaction) - return await async_transactions.fill_transaction_defaults( + return await async_fill_transaction_defaults( w3, prepared_transaction) - # return prepared_transaction - def find_functions_by_identifier( contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] From 6bf67d548f8a13bbc45801781ba10654a5ed7fb5 Mon Sep 17 00:00:00 2001 From: Don Freeman Date: Tue, 10 May 2022 15:22:46 -0400 Subject: [PATCH 63/73] Feature/asyncify contract (#2458) * removed async_fill_default from ethtester middleware * changed fill_transaction_defaults imports --- web3/contract.py | 142 ++++++++++++------------ web3/providers/eth_tester/main.py | 1 + web3/providers/eth_tester/middleware.py | 13 +-- 3 files changed, 73 insertions(+), 83 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index a212e03216..f2f69b5f25 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -49,10 +49,6 @@ HexBytes, ) -from web3._utils import ( - async_transactions, - transactions, -) from web3._utils.abi import ( abi_to_signature, check_if_arguments_can_be_encoded, @@ -67,6 +63,9 @@ merge_args_and_kwargs, receive_func_abi_exists, ) +from web3._utils.async_transactions import ( + fill_transaction_defaults as async_fill_transaction_defaults, +) from web3._utils.blocks import ( is_hex_encoded_block_hash, ) @@ -110,6 +109,9 @@ normalize_address_no_ens, normalize_bytecode, ) +from web3._utils.transactions import ( + fill_transaction_defaults, +) from web3.datastructures import ( AttributeDict, MutableAttributeDict, @@ -866,7 +868,7 @@ def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams: Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return transactions.fill_transaction_defaults(self.w3, built_transaction) + return fill_transaction_defaults(self.w3, built_transaction) @combomethod def estimate_gas( @@ -893,7 +895,7 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return async_transactions.fill_transaction_defaults(self.w3, built_transaction) + return async_fill_transaction_defaults(self.w3, built_transaction) @combomethod async def estimate_gas( @@ -1944,19 +1946,39 @@ def check_for_forbidden_api_filter_arguments( "method.") -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: +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, + ) + if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) @@ -1994,7 +2016,7 @@ def _call_contract_function(w3: 'Web3', return normalized_data -def call_contract_function( +async def async_call_contract_function( w3: 'Web3', address: ChecksumAddress, normalizers: Tuple[Callable[..., Any], ...], @@ -2021,65 +2043,47 @@ def call_contract_function( fn_kwargs=kwargs, ) - return_data = w3.eth.call( + 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) + if fn_abi is None: + fn_abi = find_matching_fn_abi(contract_abi, w3.codec, function_identifier, args, kwargs) + output_types = get_abi_output_types(fn_abi) -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, - ) + try: + output_data = w3.codec.decode_abi(output_types, return_data) + except DecodingError as e: + # Provide a more helpful error message than the one provided by + # eth-abi-utils + is_missing_code_error = ( + return_data in ACCEPTABLE_EMPTY_STRINGS + and await w3.eth.get_code(address) in ACCEPTABLE_EMPTY_STRINGS) # type: ignore + if is_missing_code_error: + msg = ( + "Could not transact with/call contract function, is contract " + "deployed correctly and chain synced?" + ) + else: + msg = ( + f"Could not decode contract function call to {function_identifier} with " + f"return data: {str(return_data)}, output_types: {output_types}" + ) + raise BadFunctionCallOutput(msg) from e - return_data = await w3.eth.call( # type: ignore - call_transaction, - block_identifier=block_id, - state_override=state_override, + _normalizers = itertools.chain( + BASE_RETURN_NORMALIZERS, + normalizers, ) + normalized_data = map_abi_data(_normalizers, output_types, output_data) - return _call_contract_function(w3, - address, - normalizers, - function_identifier, - return_data, - contract_abi, - fn_abi, - args, - kwargs) + if len(normalized_data) == 1: + return normalized_data[0] + else: + return normalized_data def parse_block_identifier(w3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier: @@ -2270,7 +2274,7 @@ def build_transaction_for_function( fn_kwargs=kwargs, ) - prepared_transaction = transactions.fill_transaction_defaults(w3, prepared_transaction) + prepared_transaction = fill_transaction_defaults(w3, prepared_transaction) return prepared_transaction @@ -2300,13 +2304,9 @@ async def async_build_transaction_for_function( fn_kwargs=kwargs, ) - # prepared_transaction = await async_transactions.fill_transaction_defaults( - # w3, prepared_transaction) - return await async_transactions.fill_transaction_defaults( + return await async_fill_transaction_defaults( w3, prepared_transaction) - # return prepared_transaction - def find_functions_by_identifier( contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index ffb20bd2e1..a4daa870df 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -55,6 +55,7 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): ) def __init__(self) -> None: + # do not import eth_tester until runtime, it is not a default dependency from eth_tester import EthereumTester from web3.providers.eth_tester.defaults import API_ENDPOINTS self.ethereum_tester = EthereumTester() diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index e230129af4..d6e6e2e485 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -331,17 +331,6 @@ 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]: @@ -372,7 +361,7 @@ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: 'eth_estimateGas', 'eth_sendTransaction', ): - filled_transaction = await async_fill_default('from', guess_from, web3, params[0]) + filled_transaction = fill_default('from', guess_from, web3, params[0]) return await make_request(method, [filled_transaction] + list(params)[1:]) else: From bf877df336e6f067fec7a6dc647a51576c8dac4f Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 10 May 2022 22:24:30 -0400 Subject: [PATCH 64/73] change to find_functions_by_identifier --- web3/contract.py | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index a677f05b58..dfe0156e73 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -650,7 +650,8 @@ def find_functions_by_identifier(cls, address: ChecksumAddress, callable_check: Callable[..., Any] ) -> List['ContractFunction']: - return find_functions_by_identifier(contract_abi, w3, address, callable_check) + return find_functions_by_identifier(contract_abi, w3, address, + callable_check, ContractFunction) # type: ignore class AsyncContract(BaseContract): @@ -762,7 +763,8 @@ def find_functions_by_identifier(cls, address: ChecksumAddress, callable_check: Callable[..., Any] ) -> List['AsyncContractFunction']: - return async_find_functions_by_identifier(contract_abi, w3, address, callable_check) + return find_functions_by_identifier(contract_abi, w3, address, + callable_check, AsyncContractFunction) # type: ignore def mk_collision_prop(fn_name: str) -> Callable[[], None]: @@ -2313,29 +2315,12 @@ async def async_build_transaction_for_function( def find_functions_by_identifier( - contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any] -) -> List[ContractFunction]: + contract_abi: ABI, w3: 'Web3', address: ChecksumAddress, callable_check: Callable[..., Any], + function_type: Union[Type[ContractFunction], Type[AsyncContractFunction]] +) -> List[Union[ContractFunction, AsyncContractFunction]]: fns_abi = filter_by_type('function', contract_abi) return [ - ContractFunction.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 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( + function_type.factory( fn_abi['name'], w3=w3, contract_abi=contract_abi, From f51aafd9fb3ec7b74a24fbc0ed5c71877d8fa5c9 Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 11 May 2022 06:05:52 -0400 Subject: [PATCH 65/73] moved get_fallback_function and get_receive_function --- web3/contract.py | 112 ++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 64 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index dfe0156e73..621c9218f1 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -540,6 +540,38 @@ def find_functions_by_identifier(cls, ) -> List[Any]: raise NotImplementedError("This method should be implemented in the inherited class") + @staticmethod + def get_fallback_function( + abi: ABI, w3: 'Web3', + function_type: Union[Type['ContractFunction'], Type['AsyncContractFunction']], + address: Optional[ChecksumAddress] = None + ) -> Union['ContractFunction', 'AsyncContractFunction']: + if abi and fallback_func_abi_exists(abi): + return function_type.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', + function_type: Union[Type['ContractFunction'], Type['AsyncContractFunction']], + address: Optional[ChecksumAddress] = None + ) -> Union['ContractFunction', 'AsyncContractFunction']: + if abi and receive_func_abi_exists(abi): + return function_type.factory( + 'receive', + w3=w3, + contract_abi=abi, + address=address, + function_identifier=ReceiveFn)() + + return cast('ContractFunction', NonExistentReceiveFunction()) + class Contract(BaseContract): @@ -568,8 +600,10 @@ 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.fallback = Contract.get_fallback_function(self.abi, self.w3, + ContractFunction, self.address) + self.receive = Contract.get_receive_function(self.abi, self.w3, + ContractFunction, self.address) @classmethod def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': @@ -591,8 +625,10 @@ def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 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) + contract.fallback = Contract.get_fallback_function(contract.abi, contract.w3, + ContractFunction) + contract.receive = Contract.get_receive_function(contract.abi, contract.w3, + ContractFunction) return contract @@ -615,34 +651,6 @@ def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': *args, **kwargs) - @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 find_functions_by_identifier(cls, contract_abi: ABI, @@ -680,8 +688,10 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: 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.fallback = AsyncContract.get_fallback_function(self.abi, self.w3, + AsyncContractFunction, self.address) + self.receive = AsyncContract.get_receive_function(self.abi, self.w3, + AsyncContractFunction, self.address) @classmethod def factory(cls, w3: 'Web3', @@ -705,8 +715,10 @@ def factory(cls, w3: 'Web3', 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) + contract.fallback = AsyncContract.get_fallback_function(contract.abi, contract.w3, + AsyncContractFunction) + contract.receive = AsyncContract.get_receive_function(contract.abi, contract.w3, + AsyncContractFunction) return contract @classmethod @@ -728,34 +740,6 @@ def constructor(cls, *args: Any, **kwargs: Any) -> 'ContractConstructor': *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, From 868ae04c966bc3798a344e456c7716b0edcd5260 Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 11 May 2022 22:24:54 -0400 Subject: [PATCH 66/73] rearranged BaseContractFunction.factory --- web3/contract.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 621c9218f1..638254567e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1213,6 +1213,11 @@ def __repr__(self) -> str: return _repr + '>' return f'' + @classmethod + def factory(cls, class_name: str, **kwargs: Any) -> Union['AsyncContractFunction', + 'ContractFunction']: + return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) + class ContractFunction(BaseContractFunction): @@ -1277,10 +1282,6 @@ def call(self, transaction: Optional[TxParams] = None, **self.kwargs ) - @classmethod - 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( @@ -1391,10 +1392,6 @@ async def call( **self.kwargs ) - @classmethod - 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( From f8638a0ee1b998ebaa86d14adde44ea22869ddd1 Mon Sep 17 00:00:00 2001 From: DB Date: Wed, 11 May 2022 22:54:25 -0400 Subject: [PATCH 67/73] fixed the type in the cast of get_recieve_function and get_fallback_function --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 638254567e..59d38609c7 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -554,7 +554,7 @@ def get_fallback_function( address=address, function_identifier=FallbackFn)() - return cast('ContractFunction', NonExistentFallbackFunction()) + return cast(function_type, NonExistentFallbackFunction()) # type: ignore @staticmethod def get_receive_function( @@ -570,7 +570,7 @@ def get_receive_function( address=address, function_identifier=ReceiveFn)() - return cast('ContractFunction', NonExistentReceiveFunction()) + return cast(function_type, NonExistentReceiveFunction()) # type: ignore class Contract(BaseContract): From 95280b28055c7aac1de11cf199fbf1b990cc1e7c Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 12 May 2022 05:43:32 -0400 Subject: [PATCH 68/73] added comment --- web3/contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web3/contract.py b/web3/contract.py index 59d38609c7..b1dfe73a7b 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1384,7 +1384,7 @@ async def call( self._return_data_normalizers, self.function_identifier, call_transaction, - block_id, # type: ignore + block_id, # type: ignore BlockIdentifier does have an Awaitable type in types.py self.contract_abi, self.abi, state_override, From aac8a810a0ce1baf81b9f8643b58dd9d447e2dae Mon Sep 17 00:00:00 2001 From: pacrob Date: Wed, 18 May 2022 11:16:29 -0600 Subject: [PATCH 69/73] add newsfragment --- newsfragments/2270.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/2270.feature.rst diff --git a/newsfragments/2270.feature.rst b/newsfragments/2270.feature.rst new file mode 100644 index 0000000000..e63b7965a8 --- /dev/null +++ b/newsfragments/2270.feature.rst @@ -0,0 +1 @@ +Add async version of contract functionality \ No newline at end of file From a8c1a626e92f8d0b5f13daa18280fa7c3e7d7738 Mon Sep 17 00:00:00 2001 From: kclowes Date: Wed, 18 May 2022 11:26:28 -0600 Subject: [PATCH 70/73] Add missing await in async build_transaction, add chain_id to eth_tester middleware --- tests/core/contracts/test_contract_constructor.py | 3 --- web3/contract.py | 2 +- web3/providers/eth_tester/middleware.py | 2 ++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/core/contracts/test_contract_constructor.py b/tests/core/contracts/test_contract_constructor.py index b57e9d417b..10d3a9d16c 100644 --- a/tests/core/contracts/test_contract_constructor.py +++ b/tests/core/contracts/test_contract_constructor.py @@ -428,7 +428,6 @@ async def test_async_contract_constructor_build_transaction_to_field_error(Async await AsyncMathContract.constructor().build_transaction({'to': '123'}) -@pytest.mark.xfail @pytest.mark.asyncio async def test_async_contract_constructor_build_transaction_no_constructor( async_w3, @@ -450,7 +449,6 @@ async def test_async_contract_constructor_build_transaction_no_constructor( assert new_txn['nonce'] == nonce -@pytest.mark.xfail @pytest.mark.asyncio async def test_async_contract_constructor_build_transaction_with_constructor_without_argument( async_w3, @@ -472,7 +470,6 @@ async def test_async_contract_constructor_build_transaction_with_constructor_wit assert new_txn['nonce'] == nonce -@pytest.mark.xfail @pytest.mark.asyncio @pytest.mark.parametrize( 'constructor_args,constructor_kwargs', diff --git a/web3/contract.py b/web3/contract.py index 4bf958b730..98e043440d 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -881,7 +881,7 @@ async def build_transaction(self, transaction: Optional[TxParams] = None) -> TxP Build the transaction dictionary without sending """ built_transaction = self._build_transaction(transaction) - return async_fill_transaction_defaults(self.w3, built_transaction) + return await async_fill_transaction_defaults(self.w3, built_transaction) @combomethod async def estimate_gas( diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index d6e6e2e485..d2096795ca 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -77,6 +77,7 @@ def is_hexstr(value: Any) -> bool: 'maxFeePerGas': 'max_fee_per_gas', 'maxPriorityFeePerGas': 'max_priority_fee_per_gas', 'accessList': 'access_list', + 'chainId': 'chain_id', } transaction_request_remapper = apply_key_map(TRANSACTION_REQUEST_KEY_MAPPING) @@ -125,6 +126,7 @@ def is_hexstr(value: Any) -> bool: 'access_list': 'accessList', 'block_hash': 'blockHash', 'block_number': 'blockNumber', + 'chain_id': 'chainId', 'gas_price': 'gasPrice', 'max_fee_per_gas': 'maxFeePerGas', 'max_priority_fee_per_gas': 'maxPriorityFeePerGas', From 3c61fdbf0be7808b17a0088aac2193a3d0a6fdf7 Mon Sep 17 00:00:00 2001 From: kclowes Date: Wed, 18 May 2022 13:16:35 -0600 Subject: [PATCH 71/73] Fix tests --- .../test_contract_build_transaction.py | 48 +++++++++---------- tests/core/eth-module/test_eth_properties.py | 12 ++--- tests/core/module-class/test_module.py | 4 +- tests/integration/test_ethereum_tester.py | 4 +- web3/_utils/module_testing/eth_module.py | 1 + web3/providers/eth_tester/defaults.py | 2 +- 6 files changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/core/contracts/test_contract_build_transaction.py b/tests/core/contracts/test_contract_build_transaction.py index 252b0c78e3..b42d9fc323 100644 --- a/tests/core/contracts/test_contract_build_transaction.py +++ b/tests/core/contracts/test_contract_build_transaction.py @@ -21,7 +21,7 @@ def test_build_transaction_not_paying_to_nonpayable_function( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -43,7 +43,7 @@ def test_build_transaction_with_contract_no_arguments(w3, math_contract, build_t 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -55,7 +55,7 @@ def test_build_transaction_with_contract_fallback_function(w3, fallback_function 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -75,7 +75,7 @@ def test_build_transaction_with_contract_class_method( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -90,7 +90,7 @@ def test_build_transaction_with_contract_default_account_is_set( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -104,7 +104,7 @@ def my_gas_price_strategy(w3, transaction_params): 'data': '0xd09de08a', 'value': 0, 'gasPrice': 5, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -133,41 +133,41 @@ def test_build_transaction_with_contract_to_address_supplied_errors(w3, {}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( {'gas': 800000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( # legacy transaction, explicit gasPrice {'gasPrice': 22 ** 8}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 61, + 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 131277322940537, }, False ), ( {'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( {'nonce': 7}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'nonce': 7, 'chainId': 61, + 'nonce': 7, 'chainId': 131277322940537, }, True ), ( {'value': 20000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 20000, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ), @@ -217,7 +217,7 @@ async def test_async_build_transaction_not_paying_to_nonpayable_function( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -244,7 +244,7 @@ async def test_async_build_transaction_with_contract_no_arguments( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -259,7 +259,7 @@ async def test_async_build_transaction_with_contract_fallback_function( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -280,7 +280,7 @@ async def test_async_build_transaction_with_contract_class_method( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -296,7 +296,7 @@ async def test_async_build_transaction_with_contract_default_account_is_set( 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 10 ** 9, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -314,7 +314,7 @@ def my_gas_price_strategy(async_w3, transaction_params): 'data': '0xd09de08a', 'value': 0, 'gasPrice': 5, - 'chainId': 61, + 'chainId': 131277322940537, } @@ -348,41 +348,41 @@ async def test_async_build_transaction_with_contract_to_address_supplied_errors( {}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( {'gas': 800000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( # legacy transaction, explicit gasPrice {'gasPrice': 22 ** 8}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 - 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 61, + 'value': 0, 'gasPrice': 22 ** 8, 'chainId': 131277322940537, }, False ), ( {'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 22 ** 8, 'maxPriorityFeePerGas': 22 ** 8, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ( {'nonce': 7}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 0, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'nonce': 7, 'chainId': 61, + 'nonce': 7, 'chainId': 131277322940537, }, True ), ( {'value': 20000}, (5,), {}, { 'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', # noqa: E501 'value': 20000, 'maxFeePerGas': 2750000000, 'maxPriorityFeePerGas': 1000000000, - 'chainId': 61, + 'chainId': 131277322940537, }, False ), ), diff --git a/tests/core/eth-module/test_eth_properties.py b/tests/core/eth-module/test_eth_properties.py index c24070eb60..3ebcb0d05a 100644 --- a/tests/core/eth-module/test_eth_properties.py +++ b/tests/core/eth-module/test_eth_properties.py @@ -30,30 +30,30 @@ def test_eth_protocolVersion(w3): def test_eth_chain_id(w3): - assert w3.eth.chain_id == 61 + assert w3.eth.chain_id == 131277322940537 # from fixture generation file def test_eth_chainId(w3): with pytest.warns(DeprecationWarning): - assert w3.eth.chainId == 61 + assert w3.eth.chainId == 131277322940537 def test_set_chain_id(w3): - assert w3.eth.chain_id == 61 + assert w3.eth.chain_id == 131277322940537 w3.eth.chain_id = 72 assert w3.eth.chain_id == 72 w3.eth.chain_id = None - assert w3.eth.chain_id == 61 + assert w3.eth.chain_id == 131277322940537 @pytest.mark.asyncio async def test_async_set_chain_id(async_w3): - assert await async_w3.eth.chain_id == 61 + assert await async_w3.eth.chain_id == 131277322940537 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 + assert await async_w3.eth.chain_id == 131277322940537 diff --git a/tests/core/module-class/test_module.py b/tests/core/module-class/test_module.py index ec67fa8d46..31b634788d 100644 --- a/tests/core/module-class/test_module.py +++ b/tests/core/module-class/test_module.py @@ -32,8 +32,8 @@ def test_attach_methods_to_module(web3_with_external_modules): 'method1': Method('eth_getBalance'), }) - assert w3.eth.chain_id == 61 - assert w3.module1.property1 == 61 + assert w3.eth.chain_id == 131277322940537 + assert w3.module1.property1 == 131277322940537 coinbase = w3.eth.coinbase assert w3.eth.get_balance(coinbase, 'latest') == 1000000000000000000000000 diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index c688fa1f14..0388dc2698 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -411,13 +411,13 @@ def test_eth_estimate_gas_with_block(self, def test_eth_chain_id(self, w3): chain_id = w3.eth.chain_id assert is_integer(chain_id) - assert chain_id == 61 + assert chain_id == 131277322940537 def test_eth_chainId(self, w3): with pytest.warns(DeprecationWarning): chain_id = w3.eth.chainId assert is_integer(chain_id) - assert chain_id == 61 + assert chain_id == 131277322940537 @disable_auto_mine def test_eth_wait_for_transaction_receipt_unmined(self, diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index f2fa5bee92..f4e6810032 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -2432,6 +2432,7 @@ def test_eth_modify_transaction( 'gas': 21000, 'maxPriorityFeePerGas': w3.toWei(1, 'gwei'), 'maxFeePerGas': w3.toWei(2, 'gwei'), + 'chainId': w3.eth.chain_id, } txn_hash = w3.eth.send_transaction(txn_params) diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index 544da5e863..699a1ce1b3 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -209,7 +209,7 @@ def personal_send_transaction(eth_tester: "EthereumTester", params: Any) -> HexS ), 'mining': static_return(False), 'hashrate': static_return(0), - 'chainId': static_return('0x3d'), + 'chainId': static_return(131277322940537), # from fixture generation file 'feeHistory': not_implemented, 'maxPriorityFeePerGas': static_return(10 ** 9), 'gasPrice': static_return(10 ** 9), # must be >= base fee post-London From 94e662243246fe7345ef70cdb35e8f416a70ed81 Mon Sep 17 00:00:00 2001 From: kclowes Date: Wed, 18 May 2022 13:42:13 -0600 Subject: [PATCH 72/73] Remove un-needed chain_id --- web3/_utils/module_testing/eth_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index f4e6810032..f2fa5bee92 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -2432,7 +2432,6 @@ def test_eth_modify_transaction( 'gas': 21000, 'maxPriorityFeePerGas': w3.toWei(1, 'gwei'), 'maxFeePerGas': w3.toWei(2, 'gwei'), - 'chainId': w3.eth.chain_id, } txn_hash = w3.eth.send_transaction(txn_params) From dae293a4e1acc9f0895e1688ce9adc154a809676 Mon Sep 17 00:00:00 2001 From: pacrob Date: Wed, 18 May 2022 15:21:41 -0600 Subject: [PATCH 73/73] fix conflicts --- ens/main.py | 24 +++++++++----- tests/core/eth-module/test_eth_properties.py | 35 ++------------------ tests/integration/test_ethereum_tester.py | 6 ---- web3/providers/eth_tester/middleware.py | 1 + 4 files changed, 18 insertions(+), 48 deletions(-) diff --git a/ens/main.py b/ens/main.py index 6db478e57f..a46870d6d1 100644 --- a/ens/main.py +++ b/ens/main.py @@ -67,6 +67,7 @@ if TYPE_CHECKING: from web3 import Web3 # noqa: F401 from web3.contract import ( # noqa: F401 + AsyncContract, Contract, ) from web3.providers import ( # noqa: F401 @@ -232,7 +233,7 @@ def setup_address( if address is None: address = EMPTY_ADDR_HEX transact['from'] = owner - resolver: 'Contract' = self._set_resolver(name, transact=transact) + resolver: Union['Contract', 'AsyncContract'] = self._set_resolver(name, transact=transact) return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact) def setup_name( @@ -285,7 +286,7 @@ def setup_name( self.setup_address(name, address, transact=transact) return self._setup_reverse(name, address, transact=transact) - def resolver(self, name: str) -> Optional['Contract']: + def resolver(self, name: str) -> Optional[Union['Contract', 'AsyncContract']]: """ Get the resolver for an ENS name. @@ -294,7 +295,8 @@ def resolver(self, name: str) -> Optional['Contract']: normal_name = normalize_name(name) return self._get_resolver(normal_name)[0] - def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: + def reverser(self, + target_address: ChecksumAddress) -> Optional[Union['Contract', 'AsyncContract']]: reversed_domain = address_to_reverse_domain(target_address) return self.resolver(reversed_domain) @@ -511,7 +513,7 @@ def _set_resolver( name: str, resolver_addr: Optional[ChecksumAddress] = None, transact: Optional["TxParams"] = None - ) -> 'Contract': + ) -> Union['Contract', 'AsyncContract']: if not transact: transact = {} transact = deepcopy(transact) @@ -529,7 +531,7 @@ def _get_resolver( self, normal_name: str, fn_name: str = 'addr' - ) -> Tuple[Optional['Contract'], str]: + ) -> Tuple[Optional[Union['Contract', 'AsyncContract']], str]: current_name = normal_name # look for a resolver, starting at the full name and taking the parent each time that no @@ -549,7 +551,8 @@ def _get_resolver( current_name = self.parent(current_name) def _decode_ensip10_resolve_data( - self, contract_call_result: bytes, extended_resolver: 'Contract', fn_name: str, + self, contract_call_result: bytes, + extended_resolver: Union['Contract', 'AsyncContract'], fn_name: str, ) -> Any: func = extended_resolver.get_function_by_name(fn_name) output_types = get_abi_output_types(func.abi) @@ -568,18 +571,21 @@ def _setup_reverse( transact['from'] = address return self._reverse_registrar().functions.setName(name).transact(transact) - def _type_aware_resolver(self, address: ChecksumAddress, func: str) -> 'Contract': + def _type_aware_resolver(self, + address: ChecksumAddress, + func: str) -> Union['Contract', 'AsyncContract']: return ( self._reverse_resolver_contract(address=address) if func == 'name' else self._resolver_contract(address=address) ) - def _reverse_registrar(self) -> 'Contract': + def _reverse_registrar(self) -> Union['Contract', 'AsyncContract']: addr = self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) return self.w3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR) -def _resolver_supports_interface(resolver: 'Contract', interface_id: HexStr) -> bool: +def _resolver_supports_interface(resolver: Union['Contract', 'AsyncContract'], + interface_id: HexStr) -> bool: if not any('supportsInterface' in repr(func) for func in resolver.all_functions()): return False return resolver.caller.supportsInterface(interface_id) diff --git a/tests/core/eth-module/test_eth_properties.py b/tests/core/eth-module/test_eth_properties.py index afc5b95a74..c855e22afb 100644 --- a/tests/core/eth-module/test_eth_properties.py +++ b/tests/core/eth-module/test_eth_properties.py @@ -19,41 +19,10 @@ def async_w3(): }) -def test_eth_protocol_version(w3): - with pytest.warns(DeprecationWarning): - assert w3.eth.protocol_version == '63' - - -def test_eth_protocolVersion(w3): - with pytest.warns(DeprecationWarning): - assert w3.eth.protocolVersion == '63' - - def test_eth_chain_id(w3): assert w3.eth.chain_id == 131277322940537 # from fixture generation file -def test_eth_chainId(w3): - with pytest.warns(DeprecationWarning): - assert w3.eth.chainId == 131277322940537 - - -def test_set_chain_id(w3): - assert w3.eth.chain_id == 131277322940537 - - w3.eth.chain_id = 72 - assert w3.eth.chain_id == 72 - - w3.eth.chain_id = None - assert w3.eth.chain_id == 131277322940537 - - @pytest.mark.asyncio -async def test_async_set_chain_id(async_w3): - assert await async_w3.eth.chain_id == 131277322940537 - - 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 == 131277322940537 \ No newline at end of file +async def test_async_eth_chain_id(async_w3): + assert await async_w3.eth.chain_id == 131277322940537 # from fixture generation file diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index 9007fe8f29..6ebe0c0618 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -392,12 +392,6 @@ def test_eth_chain_id(self, w3): assert is_integer(chain_id) assert chain_id == 131277322940537 - def test_eth_chainId(self, w3): - with pytest.warns(DeprecationWarning): - chain_id = w3.eth.chainId - assert is_integer(chain_id) - assert chain_id == 131277322940537 - @disable_auto_mine def test_eth_wait_for_transaction_receipt_unmined(self, eth_tester, diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index d2096795ca..cca6e8d000 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -83,6 +83,7 @@ def is_hexstr(value: Any) -> bool: TRANSACTION_REQUEST_FORMATTERS = { + 'chainId': to_integer_if_hex, 'gas': to_integer_if_hex, 'gasPrice': to_integer_if_hex, 'value': to_integer_if_hex,