From 979d92825012cf0558f86527da999e51ad237a35 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 25 Jan 2022 05:30:00 -0500 Subject: [PATCH 1/5] fixed ens contract function called twice --- ens/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ens/main.py b/ens/main.py index 0e22d32075..4d62e35690 100644 --- a/ens/main.py +++ b/ens/main.py @@ -245,7 +245,7 @@ def resolve(self, name: str, get: str = 'addr') -> Optional[Union[ChecksumAddres address = lookup_function(namehash).call() if is_none_or_zero_address(address): return None - return lookup_function(namehash).call() + return address else: return None From dcc5e12c495f99628c8921865fee4d45dd46890b Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 25 Jan 2022 05:40:59 -0500 Subject: [PATCH 2/5] newsfragment --- newsfragments/2318.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/2318.bugfix.rst diff --git a/newsfragments/2318.bugfix.rst b/newsfragments/2318.bugfix.rst new file mode 100644 index 0000000000..7b69a5600c --- /dev/null +++ b/newsfragments/2318.bugfix.rst @@ -0,0 +1 @@ +In ENS the contract function to resolve an ENS address was being called twice in error. One of those calls was removed. \ No newline at end of file From 49ad377060caea4e5010a547547fb173fb8e450d Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 27 Jan 2022 05:56:36 -0500 Subject: [PATCH 3/5] add BaseContractFunction and AsyncContractFunction --- web3/contract.py | 286 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 213 insertions(+), 73 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index d8b7227bbd..b749ad7ff7 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -844,7 +844,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 +897,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") - - # Read "owner" public variable - addr = contract.functions.owner().call() + def _get_call_txparams(self, transaction: Optional[TxParams] = None) -> TxParams: - :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 +925,7 @@ def call( "Please ensure that this contract instance has an address." ) - block_id = parse_block_identifier(self.web3, block_identifier) - - return call_contract_function( - self.web3, - 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 +1065,106 @@ def __repr__(self) -> str: return '' % self.fn_name +class ContractFunction(BaseContractFunction): + is_async = False + + 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.web3, block_identifier) + + return call_contract_function(self.web3, + 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 AsyncContractFuntion(BaseContractFunction): + is_async = True + + async def async_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 parse_block_identifier(self.web3, block_identifier) + + return await call_contract_function( + self.web3, + 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 @@ -1471,39 +1530,19 @@ def check_for_forbidden_api_filter_arguments( "method.") -def call_contract_function( - web3: '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(web3: '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, - web3, - fn_identifier=function_identifier, - contract_abi=contract_abi, - fn_abi=fn_abi, - transaction=transaction, - fn_args=args, - fn_kwargs=kwargs, - ) - - return_data = web3.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, web3.codec, function_identifier, args, kwargs) @@ -1541,6 +1580,94 @@ def call_contract_function( return normalized_data +def call_contract_function( + web3: '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, + web3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = web3.eth.call( + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(web3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + +async def async_call_contract_function( + web3: '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, + web3, + fn_identifier=function_identifier, + contract_abi=contract_abi, + fn_abi=fn_abi, + transaction=transaction, + fn_args=args, + fn_kwargs=kwargs, + ) + + return_data = await web3.eth.call( + call_transaction, + block_identifier=block_id, + state_override=state_override, + ) + + return _call_contract_function(web3, + address, + normalizers, + function_identifier, + return_data, + contract_abi, + fn_abi, + args, + kwargs) + + def parse_block_identifier(web3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier: if isinstance(block_identifier, int): return parse_block_identifier_int(web3, block_identifier) @@ -1552,6 +1679,19 @@ def parse_block_identifier(web3: 'Web3', block_identifier: BlockIdentifier) -> B raise BlockNumberOutofRange +async def async_parse_block_identifier(web3: 'Web3', + block_identifier: BlockIdentifier + ) -> BlockIdentifier: + if isinstance(block_identifier, int): + return parse_block_identifier_int(web3, 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 web3.eth.get_block(block_identifier)['number'] + else: + raise BlockNumberOutofRange + + def parse_block_identifier_int(web3: 'Web3', block_identifier_int: int) -> BlockNumber: if block_identifier_int >= 0: block_num = block_identifier_int From cafe160ca321baa9a313c125bae962d52f2fc243 Mon Sep 17 00:00:00 2001 From: DB Date: Tue, 1 Mar 2022 05:59:31 -0500 Subject: [PATCH 4/5] first time getting it all working --- ens/main.py | 351 +++++++++++++++++- .../contracts/test_contract_call_interface.py | 45 ++- web3/contract.py | 200 +++++++--- web3/main.py | 5 +- web3/providers/eth_tester/main.py | 53 ++- web3/providers/eth_tester/middleware.py | 240 +++++++----- 6 files changed, 717 insertions(+), 177 deletions(-) diff --git a/ens/main.py b/ens/main.py index 2c8360682a..d306cb2546 100644 --- a/ens/main.py +++ b/ens/main.py @@ -65,16 +65,7 @@ ) -class ENS: - """ - Quick access to common Ethereum Name Service functions, - like getting the address for a name. - - Unless otherwise specified, all addresses are assumed to be a `str` in - `checksum format `_, - like: ``"0x314159265dD8dbb310642f98f50C066173C1259b"`` - """ - +class BaseENS: @staticmethod @wraps(label_to_hash) def labelhash(label: str) -> HexBytes: @@ -118,6 +109,33 @@ def __init__( self.ens = self.web3.eth.contract(abi=abis.ENS, address=ens_addr) self._resolverContract = self.web3.eth.contract(abi=abis.RESOLVER) + def _assert_control(self, account: ChecksumAddress, name: str, + parent_owned: Optional[str] = None) -> None: + if not address_in(account, self.web3.eth.accounts): + raise UnauthorizedError( + "in order to modify %r, you must control account %r, which owns %r" % ( + name, account, parent_owned or name + ) + ) + +class ENS(BaseENS): + """ + Quick access to common Ethereum Name Service functions, + like getting the address for a name. + + Unless otherwise specified, all addresses are assumed to be a `str` in + `checksum format `_, + like: ``"0x314159265dD8dbb310642f98f50C066173C1259b"`` + """ + + def __init__( + self, + provider: 'BaseProvider' = cast('BaseProvider', default), + addr: ChecksumAddress = None, + middlewares: Optional[Sequence[Tuple['Middleware', str]]] = None, + ) -> None: + super().__init__(provider, addr, middlewares) + @classmethod def fromWeb3(cls, web3: 'Web3', addr: ChecksumAddress = None) -> 'ENS': """ @@ -331,15 +349,6 @@ def setup_owner( self._claim_ownership(new_owner, unowned, owned, super_owner, transact=transact) return new_owner - def _assert_control(self, account: ChecksumAddress, name: str, - parent_owned: Optional[str] = None) -> None: - if not address_in(account, self.web3.eth.accounts): - raise UnauthorizedError( - "in order to modify %r, you must control account %r, which owns %r" % ( - name, account, parent_owned or name - ) - ) - def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]: """ Takes a name, and returns the owner of the deepest subdomain that has an owner @@ -411,3 +420,307 @@ def _setup_reverse( def _reverse_registrar(self) -> 'Contract': addr = self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) return self.web3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR) + + +class AsyncENS(BaseENS): + """ + Quick access to common Ethereum Name Service functions, + like getting the address for a name. + + Unless otherwise specified, all addresses are assumed to be a `str` in + `checksum format `_, + like: ``"0x314159265dD8dbb310642f98f50C066173C1259b"`` + """ + + def __init__( + self, + provider: 'BaseProvider' = cast('BaseProvider', default), + addr: ChecksumAddress = None, + middlewares: Optional[Sequence[Tuple['Middleware', str]]] = None, + ) -> None: + super().__init__(provider, addr, middlewares) + + @classmethod + def fromWeb3(cls, web3: 'Web3', addr: ChecksumAddress = None) -> 'ENS': + """ + Generate an ENS instance with web3 + + :param `web3.Web3` web3: to infer connection information + :param hex-string addr: the address of the ENS registry on-chain. If not provided, + ENS.py will default to the mainnet ENS registry address. + """ + provider = web3.manager.provider + middlewares = web3.middleware_onion.middlewares + return cls(provider, addr=addr, middlewares=middlewares) + + async def address(self, name: str) -> Optional[ChecksumAddress]: + """ + Look up the Ethereum address that `name` currently points to. + + :param str name: an ENS name to look up + :raises InvalidName: if `name` has invalid syntax + """ + return cast(ChecksumAddress, await self.resolve(name, 'addr')) + + async def name(self, address: ChecksumAddress) -> Optional[str]: + """ + Look up the name that the address points to, using a + reverse lookup. Reverse lookup is opt-in for name owners. + + :param address: + :type address: hex-string + """ + reversed_domain = address_to_reverse_domain(address) + return await self.resolve(reversed_domain, get='name') + + async def setup_address( + self, + name: str, + address: Union[Address, ChecksumAddress, HexAddress] = cast(ChecksumAddress, default), + transact: Optional["TxParams"] = None + ) -> HexBytes: + """ + Set up the name to point to the supplied address. + The sender of the transaction must own the name, or + its parent name. + + Example: If the caller owns ``parentname.eth`` with no subdomains + and calls this method with ``sub.parentname.eth``, + then ``sub`` will be created as part of this call. + + :param str name: ENS name to set up + :param str address: name will point to this address, in checksum format. If ``None``, + erase the record. If not specified, name will point to the owner's address. + :param dict transact: the transaction configuration, like in + :meth:`~web3.eth.Eth.send_transaction` + :raises InvalidName: if ``name`` has invalid syntax + :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name` + """ + if not transact: + transact = {} + transact = deepcopy(transact) + owner = await self.setup_owner(name, transact=transact) + await self._assert_control(owner, name) + if is_none_or_zero_address(address): + address = None + elif address is default: + address = owner + elif is_binary_address(address): + address = to_checksum_address(cast(str, address)) + elif not is_checksum_address(address): + raise ValueError("You must supply the address in checksum format") + if await self.address(name) == address: + return None + if address is None: + address = EMPTY_ADDR_HEX + transact['from'] = owner + resolver: 'Contract' = await self._set_resolver(name, transact=transact) + return await resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact) + + async def setup_name( + self, + name: str, + address: Optional[ChecksumAddress] = None, + transact: Optional["TxParams"] = None + ) -> HexBytes: + """ + Set up the address for reverse lookup, aka "caller ID". + After successful setup, the method :meth:`~ens.main.ENS.name` will return + `name` when supplied with `address`. + + :param str name: ENS name that address will point to + :param str address: to set up, in checksum format + :param dict transact: the transaction configuration, like in + :meth:`~web3.eth.send_transaction` + :raises AddressMismatch: if the name does not already point to the address + :raises InvalidName: if `name` has invalid syntax + :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name` + :raises UnownedName: if no one owns `name` + """ + if not transact: + transact = {} + transact = deepcopy(transact) + if not name: + await self._assert_control(address, 'the reverse record') + return await self._setup_reverse(None, address, transact=transact) + else: + resolved = await self.address(name) + if is_none_or_zero_address(address): + 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 + ) + ) + if is_none_or_zero_address(address): + address = await self.owner(name) + if is_none_or_zero_address(address): + raise UnownedName("claim subdomain using setup_address() first") + if is_binary_address(address): + address = to_checksum_address(address) + if not is_checksum_address(address): + raise ValueError("You must supply the address in checksum format") + await self._assert_control(address, name) + if not resolved: + await self.setup_address(name, address, transact=transact) + return await self._setup_reverse(name, address, transact=transact) + + async def resolve(self, name: str, get: str = 'addr') -> Optional[Union[ChecksumAddress, str]]: + normal_name = normalize_name(name) + resolver = self.resolver(normal_name) + if resolver: + lookup_function = getattr(resolver.functions, get) + namehash = normal_name_to_hash(normal_name) + address = await lookup_function(namehash).call() + if is_none_or_zero_address(address): + return None + return address + else: + return None + + async def resolver(self, normal_name: str) -> Optional['Contract']: + resolver_addr = await 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) + + async def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: + reversed_domain = address_to_reverse_domain(target_address) + return await self.resolver(reversed_domain) + + async def owner(self, name: str) -> ChecksumAddress: + """ + Get the owner of a name. Note that this may be different from the + deed holder in the '.eth' registrar. Learn more about the difference + between deed and name ownership in the ENS `Managing Ownership docs + `_ + + :param str name: ENS name to look up + :return: owner address + :rtype: str + """ + node = raw_name_to_hash(name) + return await self.ens.caller.owner(node) + + async def setup_owner( + self, + name: str, + new_owner: ChecksumAddress = cast(ChecksumAddress, default), + transact: Optional["TxParams"] = None + ) -> ChecksumAddress: + """ + Set the owner of the supplied name to `new_owner`. + + For typical scenarios, you'll never need to call this method directly, + simply call :meth:`setup_name` or :meth:`setup_address`. This method does *not* + set up the name to point to an address. + + If `new_owner` is not supplied, then this will assume you + want the same owner as the parent domain. + + If the caller owns ``parentname.eth`` with no subdomains + and calls this method with ``sub.parentname.eth``, + then ``sub`` will be created as part of this call. + + :param str name: ENS name to set up + :param new_owner: account that will own `name`. If ``None``, set owner to empty addr. + If not specified, name will point to the parent domain owner's address. + :param dict transact: the transaction configuration, like in + :meth:`~web3.eth.Eth.send_transaction` + :raises InvalidName: if `name` has invalid syntax + :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name` + :returns: the new owner's address + """ + if not transact: + transact = {} + transact = deepcopy(transact) + (super_owner, unowned, owned) = await self._first_owner(name) + if new_owner is default: + new_owner = super_owner + elif not new_owner: + new_owner = ChecksumAddress(EMPTY_ADDR_HEX) + else: + new_owner = to_checksum_address(new_owner) + current_owner = await self.owner(name) + if new_owner == EMPTY_ADDR_HEX and not current_owner: + return None + elif current_owner == new_owner: + return current_owner + else: + await self._assert_control(super_owner, name, owned) + await self._claim_ownership(new_owner, unowned, owned, super_owner, transact=transact) + return new_owner + + async def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]: + """ + Takes a name, and returns the owner of the deepest subdomain that has an owner + + :returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain) + """ + owner = None + unowned = [] + pieces = normalize_name(name).split('.') + while pieces and is_none_or_zero_address(owner): + name = '.'.join(pieces) + owner = await self.owner(name) + if is_none_or_zero_address(owner): + unowned.append(pieces.pop(0)) + return (owner, unowned, name) + + async def _claim_ownership( + self, + owner: ChecksumAddress, + unowned: Sequence[str], + owned: str, + old_owner: Optional[ChecksumAddress] = None, + transact: Optional["TxParams"] = None + ) -> None: + if not transact: + transact = {} + transact = deepcopy(transact) + transact['from'] = old_owner or owner + for label in reversed(unowned): + await self.ens.functions.setSubnodeOwner( + raw_name_to_hash(owned), + label_to_hash(label), + owner + ).transact(transact) + owned = "%s.%s" % (label, owned) + + async def _set_resolver( + self, + name: str, + resolver_addr: Optional[ChecksumAddress] = None, + transact: Optional["TxParams"] = None + ) -> 'Contract': + if not transact: + transact = {} + transact = deepcopy(transact) + if is_none_or_zero_address(resolver_addr): + resolver_addr = await self.address('resolver.eth') + namehash = raw_name_to_hash(name) + if await self.ens.caller.resolver(namehash) != resolver_addr: + await self.ens.functions.setResolver( + namehash, + resolver_addr + ).transact(transact) + return await self._resolverContract(address=resolver_addr) + + async def _setup_reverse( + self, name: str, address: ChecksumAddress, transact: Optional["TxParams"] = None + ) -> HexBytes: + if not transact: + transact = {} + transact = deepcopy(transact) + if name: + name = normalize_name(name) + else: + name = '' + transact['from'] = address + return await self._reverse_registrar().functions.setName(name).transact(transact) + + async def _reverse_registrar(self) -> 'Contract': + addr = await self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) + return await self.web3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR) \ No newline at end of file diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 1c6b0d91d2..63c3f4ca51 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -2,6 +2,7 @@ Decimal, getcontext, ) +from dis import Bytecode from distutils.version import ( LooseVersion, ) @@ -21,10 +22,13 @@ from hexbytes import ( HexBytes, ) +from ens.main import AsyncENS from web3._utils.ens import ( contract_ens_addresses, ) +from web3.contract import AsyncContract, Contract +from web3.eth import AsyncEth from web3.exceptions import ( BadFunctionCallOutput, BlockNumberOutofRange, @@ -35,6 +39,9 @@ NoABIFunctionsFound, ValidationError, ) +from web3.main import Web3 +from web3.middleware.buffered_gas_estimate import async_buffered_gas_estimate_middleware +from web3.providers.eth_tester.main import AsyncEthereumTesterProvider def deploy(web3, Contract, apply_func=identity, args=None): @@ -58,7 +65,6 @@ def address_reflector_contract(web3, AddressReflectorContract, address_conversio def math_contract(web3, MathContract, address_conversion_func): return deploy(web3, MathContract, address_conversion_func) - @pytest.fixture() def string_contract(web3, StringContract, address_conversion_func): return deploy(web3, StringContract, address_conversion_func, args=["Caqalai"]) @@ -879,3 +885,40 @@ 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}) + +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() +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_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() +async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): + return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) + +@pytest.mark.asyncio +async def test_async_call_no_args(async_math_contract): + result = await async_math_contract.functions.return13().call() + assert result == 13 \ No newline at end of file diff --git a/web3/contract.py b/web3/contract.py index b749ad7ff7..746e01c661 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -151,11 +151,17 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] -class ContractFunctions: +class BaseContractFunctions: """Class containing contract function objects """ - def __init__(self, abi: ABI, web3: 'Web3', address: Optional[ChecksumAddress] = None) -> None: + def __init__(self, + abi: ABI, + web3: 'Web3', + #TODO: Typing isn't working here + contract_function_class, + address: Optional[ChecksumAddress] = None) -> None: + self.abi = abi self.web3 = web3 self.address = address @@ -166,7 +172,7 @@ def __init__(self, abi: ABI, web3: 'Web3', address: Optional[ChecksumAddress] = setattr( self, func['name'], - ContractFunction.factory( + contract_function_class.factory( func['name'], web3=self.web3, contract_abi=self.abi, @@ -207,6 +213,22 @@ def __hasattr__(self, event_name: str) -> bool: except ABIFunctionNotFound: return False +class ContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + web3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, web3, ContractFunction, address) + +class AsyncContractFunctions(BaseContractFunctions): + def __init__(self, + abi: ABI, + web3: 'Web3', + address: Optional[ChecksumAddress] = None, + ) -> None: + super().__init__(abi, web3, AsyncContractFunction, address) + class ContractEvents: """Class containing contract event objects @@ -276,7 +298,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 +348,16 @@ 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.web3 is None: - raise AttributeError( - 'The `Contract` class has not been initialized. Please use the ' - '`web3.contract` interface to create your contract class.' - ) + :param address: Contract address as 0x hex string""" - if address: - self.address = normalize_address(self.web3.ens, address) - - if not self.address: - raise TypeError("The address argument is required to instantiate a contract.") - - self.functions = ContractFunctions(self.abi, self.web3, self.address) self.caller = ContractCaller(self.abi, self.web3, self.address) self.events = ContractEvents(self.abi, self.web3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.web3, self.address) self.receive = Contract.get_receive_function(self.abi, self.web3, self.address) @classmethod - def factory(cls, web3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': - - kwargs['web3'] = web3 - - normalizers = { - 'abi': normalize_abi, - 'address': partial(normalize_address, kwargs['web3'].ens), - 'bytecode': normalize_bytecode, - 'bytecode_runtime': normalize_bytecode, - } + def factory(cls, contract) -> 'BaseContract': - contract = cast(Contract, PropertyCheckingFactory( - class_name or cls.__name__, - (cls,), - kwargs, - normalizers=normalizers, - )) - contract.functions = ContractFunctions(contract.abi, contract.web3) contract.caller = ContractCaller(contract.abi, contract.web3, contract.address) contract.events = ContractEvents(contract.abi, contract.web3) contract.fallback = Contract.get_fallback_function(contract.abi, contract.web3) @@ -583,6 +576,85 @@ 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.web3 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.web3.ens, address) + + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + + self.functions = ContractFunctions(self.abi, self.web3, self.address) + super().__init__(address) + + @classmethod + def factory(cls, web3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + kwargs['web3'] = web3 + + normalizers = { + 'abi': normalize_abi, + 'address': partial(normalize_address, kwargs['web3'].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.web3) + contract = super().factory(contract) + return contract + +class AsyncContract(BaseContract): + + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: + if self.web3 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.web3.ens, address) + + if not self.address: + raise TypeError("The address argument is required to instantiate a contract.") + self.functions = AsyncContractFunctions(self.abi, self.web3, self.address) + super().__init__(address) + + @classmethod + def factory(cls, web3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + kwargs['web3'] = web3 + + normalizers = { + 'abi': normalize_abi, + 'address': partial(normalize_address, kwargs['web3'].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.web3) + 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 +663,7 @@ def collision_fn() -> NoReturn: return collision_fn -class ContractConstructor: +class BaseContractConstructor: """ Class for contract constructor API. """ @@ -644,8 +716,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 +730,7 @@ def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: transact_transaction['data'] = self.data_in_transaction - # TODO: handle asynchronous contract creation - return self.web3.eth.send_transaction(transact_transaction) + return transact_transaction @combomethod def buildTransaction(self, transaction: Optional[TxParams] = None) -> TxParams: @@ -694,6 +764,19 @@ def check_forbidden_keys_in_transaction( ) +class ContractConstructor(BaseContractConstructor): + + @combomethod + def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return self.web3.eth.send_transaction(self._get_transaction(transaction)) + +class AsyncContractConstructor(BaseContractConstructor): + + @combomethod + async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: + return await self.web3.eth.send_transaction(self._get_transaction(transaction)) + + class ConciseMethod: ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} @@ -1066,7 +1149,6 @@ def __repr__(self) -> str: class ContractFunction(BaseContractFunction): - is_async = False def call(self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', @@ -1114,10 +1196,9 @@ def call(self, transaction: Optional[TxParams] = None, ) -class AsyncContractFuntion(BaseContractFunction): - is_async = True +class AsyncContractFunction(BaseContractFunction): - async def async_call( + async def call( self, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', state_override: Optional[CallOverrideParams] = None, @@ -1148,9 +1229,9 @@ async def async_call( """ call_transaction = self._get_call_txparams(transaction) - block_id = await parse_block_identifier(self.web3, block_identifier) + block_id = await async_parse_block_identifier(self.web3, block_identifier) - return await call_contract_function( + return await async_call_contract_function( self.web3, self.address, self._return_data_normalizers, @@ -1407,7 +1488,7 @@ def factory(cls, class_name: str, **kwargs: Any) -> PropertyCheckingFactory: return PropertyCheckingFactory(class_name, (cls,), kwargs) -class ContractCaller: +class BaseContractCaller: """ An alternative Contract API. @@ -1434,7 +1515,8 @@ def __init__(self, web3: 'Web3', address: ChecksumAddress, transaction: Optional[TxParams] = None, - block_identifier: BlockIdentifier = 'latest') -> None: + block_identifier: BlockIdentifier = 'latest', + contract_function_class: Optional[BaseContractFunction] = ContractFunction) -> None: self.web3 = web3 self.address = address self.abi = abi @@ -1446,7 +1528,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'], web3=self.web3, contract_abi=self.abi, @@ -1511,6 +1593,26 @@ def call_function( transaction = {} return fn(*args, **kwargs).call(transaction, block_identifier) +class ContractCaller(BaseContractCaller): + def __init__(self, + abi: ABI, + web3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, web3, address, + transaction, block_identifier, ContractFunction) + +class AsyncContractCaller(BaseContractCaller): + + def __init__(self, + abi: ABI, + web3: 'Web3', + address: ChecksumAddress, + transaction: Optional[TxParams] = None, + block_identifier: BlockIdentifier = 'latest') -> None: + super().__init__(abi, web3, address, + transaction, block_identifier, AsyncContractFunction) def check_for_forbidden_api_filter_arguments( event_abi: ABIEvent, _filters: Dict[str, Any] diff --git a/web3/main.py b/web3/main.py index acbac1b9a3..2f4f4653b4 100644 --- a/web3/main.py +++ b/web3/main.py @@ -46,6 +46,7 @@ ) from ens import ENS +from ens.main import BaseENS from web3._utils.abi import ( build_default_registry, build_strict_registry, @@ -346,14 +347,14 @@ def is_encodable(self, _type: TypeStr, value: Any) -> bool: return self.codec.is_encodable(_type, value) @property - def ens(self) -> ENS: + def ens(self) -> BaseENS: if self._ens is cast(ENS, empty): return ENS.fromWeb3(self) else: return self._ens @ens.setter - def ens(self, new_ens: ENS) -> None: + def ens(self, new_ens: BaseENS) -> None: self._ens = new_ens @property diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index e45df5082b..befc02491c 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -1,3 +1,4 @@ +import asyncio from typing import ( TYPE_CHECKING, Any, @@ -13,24 +14,29 @@ from eth_abi.exceptions import ( InsufficientDataBytes, ) +from web3 import middleware 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, default_transaction_fields_middleware, ethereum_tester_middleware, + async_ethereum_tester_middleware, ) if TYPE_CHECKING: @@ -43,13 +49,50 @@ 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: + 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 + + + - async def make_request( - self, method: RPCEndpoint, params: Any - ) -> RPCResponse: - return self.eth_tester.make_request(method, params) class EthereumTesterProvider(BaseProvider): diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index fb865d8aab..abaefb0caf 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -1,3 +1,4 @@ +import asyncio import operator from typing import ( TYPE_CHECKING, @@ -26,6 +27,7 @@ partial, pipe, ) +from web3 import middleware from web3._utils.formatters import ( apply_formatter_to_array, @@ -39,6 +41,7 @@ from web3.middleware import ( construct_formatting_middleware, ) +from web3.middleware.formatting import async_construct_formatting_middleware from web3.types import ( RPCEndpoint, RPCResponse, @@ -181,109 +184,117 @@ 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): + 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 +324,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], web3: "Web3" ) -> Callable[[RPCEndpoint, Any], RPCResponse]: @@ -332,3 +354,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:]) + else: + return await make_request(method, params) + return middleware From 8e9617984aab95a9497f76330199f240f8ef5140 Mon Sep 17 00:00:00 2001 From: DB Date: Thu, 3 Mar 2022 20:39:40 -0500 Subject: [PATCH 5/5] linting --- ens/__init__.py | 1 + ens/main.py | 29 +++--- tests/core/contracts/conftest.py | 52 ++++++++++ .../contracts/test_contract_call_interface.py | 41 +------- web3/contract.py | 99 ++++++++++--------- web3/main.py | 8 +- web3/providers/eth_tester/main.py | 21 ++-- web3/providers/eth_tester/middleware.py | 14 ++- 8 files changed, 149 insertions(+), 116 deletions(-) diff --git a/ens/__init__.py b/ens/__init__.py index c3a463a5da..09c98f2922 100644 --- a/ens/__init__.py +++ b/ens/__init__.py @@ -2,6 +2,7 @@ from .main import ( ENS, + AsyncENS ) from .exceptions import ( diff --git a/ens/main.py b/ens/main.py index efba130066..35e4ea733a 100644 --- a/ens/main.py +++ b/ens/main.py @@ -6,6 +6,7 @@ ) from typing import ( TYPE_CHECKING, + Awaitable, Optional, Sequence, Tuple, @@ -54,6 +55,7 @@ if TYPE_CHECKING: from web3 import Web3 # noqa: F401 from web3.contract import ( # noqa: F401 + AsyncContract, Contract, ) from web3.providers import ( # noqa: F401 @@ -118,6 +120,7 @@ def _assert_control(self, account: ChecksumAddress, name: str, ) ) + class ENS(BaseENS): """ Quick access to common Ethereum Name Service functions, @@ -134,7 +137,7 @@ def __init__( addr: ChecksumAddress = None, middlewares: Optional[Sequence[Tuple['Middleware', str]]] = None, ) -> None: - super().__init__(provider, addr, middlewares) + super().__init__(provider, addr, middlewares) @classmethod def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> 'ENS': @@ -420,7 +423,7 @@ def _setup_reverse( def _reverse_registrar(self) -> 'Contract': addr = self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) return self.w3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR) - + class AsyncENS(BaseENS): """ @@ -438,10 +441,10 @@ def __init__( addr: ChecksumAddress = None, middlewares: Optional[Sequence[Tuple['Middleware', str]]] = None, ) -> None: - super().__init__(provider, addr, middlewares) + super().__init__(provider, addr, middlewares) @classmethod - def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> 'ENS': + def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> Awaitable['AsyncENS']: """ Generate an ENS instance with web3 @@ -453,7 +456,7 @@ def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> 'ENS': middlewares = w3.middleware_onion.middlewares return cls(provider, addr=addr, middlewares=middlewares) - async def address(self, name: str) -> Optional[ChecksumAddress]: + async def address(self, name: str) -> Awaitable[Optional[ChecksumAddress]]: """ Look up the Ethereum address that `name` currently points to. @@ -462,7 +465,7 @@ async def address(self, name: str) -> Optional[ChecksumAddress]: """ return cast(ChecksumAddress, await self.resolve(name, 'addr')) - async def name(self, address: ChecksumAddress) -> Optional[str]: + async def name(self, address: ChecksumAddress) -> Awaitable[Optional[str]]: """ Look up the name that the address points to, using a reverse lookup. Reverse lookup is opt-in for name owners. @@ -478,7 +481,7 @@ async def setup_address( name: str, address: Union[Address, ChecksumAddress, HexAddress] = cast(ChecksumAddress, default), transact: Optional["TxParams"] = None - ) -> HexBytes: + ) -> Awaitable[HexBytes]: """ Set up the name to point to the supplied address. The sender of the transaction must own the name, or @@ -509,7 +512,7 @@ async def setup_address( address = to_checksum_address(cast(str, address)) elif not is_checksum_address(address): raise ValueError("You must supply the address in checksum format") - if await self.address(name) == address: + if await self.address(name) == address: # type: ignore return None if address is None: address = EMPTY_ADDR_HEX @@ -522,7 +525,7 @@ async def setup_name( name: str, address: Optional[ChecksumAddress] = None, transact: Optional["TxParams"] = None - ) -> HexBytes: + ) -> Awaitable[HexBytes]: """ Set up the address for reverse lookup, aka "caller ID". After successful setup, the method :meth:`~ens.main.ENS.name` will return @@ -567,7 +570,8 @@ async def setup_name( await self.setup_address(name, address, transact=transact) return await self._setup_reverse(name, address, transact=transact) - async def resolve(self, name: str, get: str = 'addr') -> Optional[Union[ChecksumAddress, str]]: + async def resolve(self, name: str, + get: str = 'addr') -> Awaitable[Optional[Union[ChecksumAddress, str]]]: normal_name = normalize_name(name) resolver = self.resolver(normal_name) if resolver: @@ -580,13 +584,14 @@ async def resolve(self, name: str, get: str = 'addr') -> Optional[Union[Checksum else: return None - async def resolver(self, normal_name: str) -> Optional['Contract']: + async def resolver(self, normal_name: str) -> Awaitable[Optional['AsyncContract']]: resolver_addr = await 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) - async def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']: + async def reverser(self, + target_address: ChecksumAddress) -> Awaitable[Optional['AsyncContract']]: reversed_domain = address_to_reverse_domain(target_address) return await self.resolver(reversed_domain) 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/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index b4326d27ce..8946a290df 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -2,7 +2,6 @@ Decimal, getcontext, ) -from dis import Bytecode from distutils.version import ( LooseVersion, ) @@ -22,13 +21,10 @@ from hexbytes import ( HexBytes, ) -from ens.main import AsyncENS from web3._utils.ens import ( contract_ens_addresses, ) -from web3.contract import AsyncContract, Contract -from web3.eth import AsyncEth from web3.exceptions import ( BadFunctionCallOutput, BlockNumberOutofRange, @@ -39,9 +35,6 @@ NoABIFunctionsFound, ValidationError, ) -from web3.main import Web3 -from web3.middleware.buffered_gas_estimate import async_buffered_gas_estimate_middleware -from web3.providers.eth_tester.main import AsyncEthereumTesterProvider def deploy(w3, Contract, apply_func=identity, args=None): @@ -65,6 +58,7 @@ def address_reflector_contract(w3, AddressReflectorContract, address_conversion_ 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"]) @@ -886,39 +880,8 @@ def test_call_revert_contract(revert_contract): # value. revert_contract.functions.revertWithMessage().call({'gas': 100000}) -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() -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_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() -async def async_math_contract(async_w3, AsyncMathContract, address_conversion_func): - return await async_deploy(async_w3, AsyncMathContract, address_conversion_func) @pytest.mark.asyncio async def test_async_call_no_args(async_math_contract): result = await async_math_contract.functions.return13().call() - assert result == 13 \ No newline at end of file + assert result == 13 diff --git a/web3/contract.py b/web3/contract.py index 5553fa2535..89765e2448 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -155,11 +155,11 @@ class BaseContractFunctions: """Class containing contract function objects """ - def __init__(self, - abi: ABI, - w3: 'Web3', - #TODO: Typing isn't working here - contract_function_class, + def __init__(self, + abi: ABI, + w3: 'Web3', + contract_function_class: Union[Type['ContractFunction'], + Type['AsyncContractFunction']], address: Optional[ChecksumAddress] = None) -> None: self.abi = abi @@ -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, + for func in self._functions: + setattr( + self, + func['name'], + contract_function_class.factory( func['name'], - contract_function_class.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: @@ -213,18 +213,20 @@ def __hasattr__(self, event_name: str) -> bool: except ABIFunctionNotFound: return False + class ContractFunctions(BaseContractFunctions): - def __init__(self, - abi: ABI, - w3: 'Web3', - address: Optional[ChecksumAddress] = None, - ) -> None: + 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', + def __init__(self, + abi: ABI, + w3: 'Web3', address: Optional[ChecksumAddress] = None, ) -> None: super().__init__(abi, w3, AsyncContractFunction, address) @@ -356,7 +358,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) @classmethod - def factory(cls, contract) -> 'BaseContract': + 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) @@ -577,7 +581,7 @@ def _encode_constructor_data(cls, args: Optional[Any] = None, class Contract(BaseContract): - + def __init__(self, address: Optional[ChecksumAddress] = None) -> None: if self.w3 is None: raise AttributeError( @@ -593,7 +597,7 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: 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 @@ -612,9 +616,10 @@ def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> normalizers=normalizers, )) contract.functions = ContractFunctions(contract.abi, contract.w3) - contract = super().factory(contract) + contract = BaseContract.factory(contract) return contract + class AsyncContract(BaseContract): def __init__(self, address: Optional[ChecksumAddress] = None) -> None: @@ -633,7 +638,9 @@ def __init__(self, address: Optional[ChecksumAddress] = None) -> None: super().__init__(address) @classmethod - def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + def factory(cls, w3: 'Web3', + class_name: Optional[str] = None, + **kwargs: Any) -> 'AsyncContract': kwargs['w3'] = w3 normalizers = { @@ -650,11 +657,10 @@ def factory(cls, w3: 'Web3', class_name: Optional[str] = None, **kwargs: Any) -> normalizers=normalizers, )) contract.functions = AsyncContractFunctions(contract.abi, contract.w3) - contract = BaseContract.factory(contract) + 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) @@ -770,6 +776,7 @@ class ContractConstructor(BaseContractConstructor): def transact(self, transaction: Optional[TxParams] = None) -> HexBytes: return self.w3.eth.send_transaction(self._get_transaction(transaction)) + class AsyncContractConstructor(BaseContractConstructor): @combomethod @@ -1516,7 +1523,8 @@ def __init__(self, address: ChecksumAddress, transaction: Optional[TxParams] = None, block_identifier: BlockIdentifier = 'latest', - contract_function_class: Optional[BaseContractFunction] = ContractFunction) -> None: + contract_function_class: Optional[BaseContractFunction] = ContractFunction + ) -> None: self.w3 = w3 self.address = address self.abi = abi @@ -1593,26 +1601,29 @@ def call_function( 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) + 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) + 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] diff --git a/web3/main.py b/web3/main.py index 2f4f4653b4..c7ebdd125b 100644 --- a/web3/main.py +++ b/web3/main.py @@ -46,7 +46,7 @@ ) from ens import ENS -from ens.main import BaseENS +from ens.main import AsyncENS, BaseENS from web3._utils.abi import ( build_default_registry, build_strict_registry, @@ -239,7 +239,7 @@ def __init__( middlewares: Optional[Sequence[Any]] = None, modules: Optional[Dict[str, Union[Type[Module], Sequence[Any]]]] = None, external_modules: Optional[Dict[str, Union[Type[Module], Sequence[Any]]]] = None, - ens: ENS = cast(ENS, empty) + ens: Union[ENS, AsyncENS] = cast(ENS, empty) ) -> None: self.manager = self.RequestManager(self, provider, middlewares) # this codec gets used in the module initialization, @@ -347,14 +347,14 @@ def is_encodable(self, _type: TypeStr, value: Any) -> bool: return self.codec.is_encodable(_type, value) @property - def ens(self) -> BaseENS: + def ens(self) -> Union['ENS', 'AsyncENS']: if self._ens is cast(ENS, empty): return ENS.fromWeb3(self) else: return self._ens @ens.setter - def ens(self, new_ens: BaseENS) -> None: + def ens(self, new_ens: Union['ENS', 'AsyncENS']) -> None: self._ens = new_ens @property diff --git a/web3/providers/eth_tester/main.py b/web3/providers/eth_tester/main.py index befc02491c..987b6e0000 100644 --- a/web3/providers/eth_tester/main.py +++ b/web3/providers/eth_tester/main.py @@ -1,4 +1,3 @@ -import asyncio from typing import ( TYPE_CHECKING, Any, @@ -14,19 +13,22 @@ from eth_abi.exceptions import ( InsufficientDataBytes, ) -from web3 import middleware from web3._utils.compat import ( Literal, ) -from web3.middleware.buffered_gas_estimate import async_buffered_gas_estimate_middleware +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.providers.eth_tester.defaults import ( + API_ENDPOINTS, +) from web3.types import ( RPCEndpoint, RPCResponse, @@ -34,9 +36,9 @@ from .middleware import ( async_default_transaction_fields_middleware, + async_ethereum_tester_middleware, default_transaction_fields_middleware, ethereum_tester_middleware, - async_ethereum_tester_middleware, ) if TYPE_CHECKING: @@ -49,13 +51,12 @@ class AsyncEthereumTesterProvider(AsyncBaseProvider): - middlewares = ( + middlewares = ( async_buffered_gas_estimate_middleware, async_default_transaction_fields_middleware, async_ethereum_tester_middleware - ) + ) - def __init__(self) -> None: from eth_tester import EthereumTester self.ethereum_tester = EthereumTester() @@ -91,10 +92,6 @@ async def isConnected(self) -> Literal[True]: return True - - - - class EthereumTesterProvider(BaseProvider): middlewares = ( default_transaction_fields_middleware, diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index a87046bf6e..eea53e14e6 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -1,4 +1,3 @@ -import asyncio import operator from typing import ( TYPE_CHECKING, @@ -27,7 +26,6 @@ partial, pipe, ) -from web3 import middleware from web3._utils.formatters import ( apply_formatter_to_array, @@ -41,8 +39,11 @@ from web3.middleware import ( construct_formatting_middleware, ) -from web3.middleware.formatting import async_construct_formatting_middleware +from web3.middleware.formatting import ( + async_construct_formatting_middleware, +) from web3.types import ( + Middleware, RPCEndpoint, RPCResponse, TxParams, @@ -286,12 +287,15 @@ def is_hexstr(value: Any) -> bool: RPCEndpoint('evm_snapshot'): integer_to_hex, } -async def async_ethereum_tester_middleware(make_request, web3): + +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=request_formatters, result_formatters=result_formatters @@ -366,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, [filled_transaction] + list(params)[1:]) + return await make_request(method, [filled_transaction] + list(params)[1:]) # type: ignore else: return await make_request(method, params) return middleware