Skip to content

Feature/asyncify contract #2394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Mar 23, 2022

Conversation

dbfreem
Copy link
Contributor

@dbfreem dbfreem commented Mar 18, 2022

More Async, I think the only thing left before test and docs is build_transaction_for_function.

I plan to work on build_transaction_for_function tomorrow.

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 19, 2022

@pacrob from what I see I think this is it! Can you look through contract and see if anything jumps out at you that needs to be async?

Also, what are your thoughts on testing. I threw a couple of test in there along the way and modified the AsyncEthereumTestProvider to enable testing. It seems a little bit too much to copy all the blocking test and make them async. I feel like we should pick out a few test here and there to do the testing. Thoughts??

I am going to go ahead and start working on the docs.

Let me know if you have any feedback.

@MrNaif2018
Copy link

MrNaif2018 commented Mar 19, 2022

Hi! Sorry for chiming in, but I think buildTransaction doesn't work yet:

contract_factory = AsyncContract.factory(self.web3, abi=self.ABI)
contract = contract_factory(contract_address)
exec_function = contract.functions.transfer(*args,**kwargs)
tx = exec_function.buildTransaction(**params)

And here tx["gasPrice"] is a coroutine of function AsyncEth.generate_gas_price
Awaiting buildTransaction doesn't work as it returns dict directly

/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/contract.py:2250: RuntimeWarning: coroutine 'AsyncEth.generate_gas_price' was never awaited
  prepared_transaction = fill_transaction_defaults(w3, prepared_transaction)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Sorry if I misunderstood something (:

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 19, 2022

@MrNaif2018. Thanks for pointing that out! I actually made that change but just realized I didn't push the commit. When I am back by my computer later I will push it. If you see anything else let me know.

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 20, 2022

@MrNaif2018 here are the changes for build_transaction.

@MrNaif2018
Copy link

@dbfreem Yeah, it works now! estimate_gas always returns execution reverted but that's definitely not related to this PR

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 20, 2022

@MrNaif2018 can you provide more details on your comment about estimate_gas I added it in a different PR. Is it not working correctly?

@MrNaif2018
Copy link

Yeah. I am using infura on kovan testnet with contract 0xb7a4F3E9097C08dA09517b5aB877F7a917224ede
Trying to call transfer call from my address 0xa26fB2d852da1f2db46C48FE23737491AfCC0417 with args (0xa26fB2d852da1f2db46C48FE23737491AfCC0417, 100000000000) (basically send everything to myself

The code used:

contract_factory = AsyncContract.factory(self.web3, abi=self.ABI)
contract = contract_factory(contract_address)
exec_function = contract.functions.transfer(address, amount_in_wei)
tx = await exec_function.build_transaction({"chainId":await self.web3.eth.chain_id,"nonce":await self.web3.eth.get_transaction_count(address)}) # also I pass maxFeePerGas and maxPriorityFeePerGas

And if I provide gas param to build_transaction it works, but if I don't it just fails with execution reverted error and no details

The more generic code actually used: https://github.com/bitcartcc/bitcart/blob/2535c781db67082bd9085b77ce932f80d222822a/daemons/eth.py#L1116-L1123
And code for getting maxFeePerGas and maxPriorityFeePerGas: https://github.com/bitcartcc/bitcart/blob/2535c781db67082bd9085b77ce932f80d222822a/daemons/eth.py#L1003-L1007

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 21, 2022

@MrNaif2018 can you post the actual stacktrace. I am not seeing right off where the issue might be and I haven't been able to reproduce it with a unit test yet.

@MrNaif2018
Copy link

MrNaif2018 commented Mar 21, 2022

Traceback (most recent call last):
  File "daemons/eth.py", line 719, in execute_method
    result = await self.get_exec_result(xpub, req_args, req_kwargs, exec_method)
  File "daemons/eth.py", line 707, in get_exec_result
    return await result if inspect.isawaitable(result) else result
  File "daemons/eth.py", line 1129, in writecontract
    tx = await exec_function.build_transaction(await self.get_common_payto_params(self.wallets[wallet].address, gas))
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/contract.py", line 1443, in build_transaction
    return await async_build_transaction_for_function(
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/contract.py", line 2301, in async_build_transaction_for_function
    prepared_transaction = await async_transactions.fill_transaction_defaults(
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/_utils/async_transactions.py", line 116, in fill_transaction_defaults
    default_val = await default_getter(w3, transaction)
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/_utils/async_transactions.py", line 31, in _estimate_gas
    return await w3.eth.estimate_gas(tx)  # type: ignore
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/eth.py", line 477, in estimate_gas
    return await self._estimate_gas(transaction, block_identifier)  # type: ignore
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/module.py", line 73, in caller
    result = await w3.manager.coro_request(method_str,
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/manager.py", line 214, in coro_request
    return self.formatted_response(response,
  File "/home/alex/envs/bitcart/lib/python3.8/site-packages/web3/manager.py", line 171, in formatted_response
    raise ValueError(response["error"])
ValueError: {'code': -32016, 'data': 'Reverted', 'message': 'The execution failed due to an exception.'}

I don't think this would help much, maybe it is something on my side.
The tx dict passed into build_transaction
{'nonce': 115, 'maxFeePerGas': 2500000014, 'maxPriorityFeePerGas': 2500000000}

By the way it doesn't seem to allow passing chainId to estimate_fee. Maybe it just executes on wrong chain because of lack of chainId? I am running it on kovan testnet

EDIT: as I've thought, this is something my side, that's why I told you it's probably not related to this PR (: Adding from field made the estimation work. Though shouldn't estimate_gas allow chainId param?

@dbfreem
Copy link
Contributor Author

dbfreem commented Mar 21, 2022

@MrNaif2018 let me know if you find anything else with the async implementation. As of now I will assume this is an issue on your end.

Thanks, for looking over this. It gives me more confidence that the Async Implementation of Contract works.

@pacrob
Copy link
Contributor

pacrob commented Mar 23, 2022

Thanks, @dbfreem! Nothing jumps out as still needing to be asynced, but I'm going to get some more eyes on this later this week. I agree on tests - can still be thorough without copying everything over. I was looking at the test_contract_estimateGas and fell down a hole trying to find an async answer for functools - I found paco, but it's no longer maintained. Do you know of anything else?

@pacrob pacrob merged commit 9ed1b7a into ethereum:asyncify-contract Mar 23, 2022
@ChristianCoenen
Copy link

ChristianCoenen commented Mar 23, 2022

@dbfreem
Hey 👋 Very happy to see that the contract logic is being anscyified 👍

Missing Attribute for AsyncEth?

I noticed that my contracts were still objects of type Contract instead of AsyncContract. So I investigated a bit and found that it looks like AsyncEth is missing the following class attribute:

defaultContractFactory: Type[Union[AsyncContract, AsyncContractCaller]] = AsyncContract

None type exception - edit: can be ignored

Afterward, I tried executing a smart contract function and noticed the following error:

Traceback (most recent call last):
  File "/Users/christiancoenen/.pyenv/versions/3.9.10/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/christiancoenen/.pyenv/versions/3.9.10/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/christiancoenen/repos/BendingSpoons/deficracker/deficracker/test.py", line 22, in test
    print(await contract.functions.name.call())
  File "/Users/christiancoenen/repos/BendingSpoons/web3.py/web3/contract.py", line 1419, in call
    return await async_call_contract_function(
TypeError: Value after * must be an iterable, not NoneType

The state before the async_call_contract_function call is:
Screenshot 2022-03-23 at 15 42 41

I figured that the __call__ method is not called before and that AsyncContractFunction does not inherit from Callable. Changing it to inherit from Callable results in the following error:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Here my code to give some more context (the abi is just the basic erc20 abi):

async def test() -> None:
    w3 = Web3(
        provider=AsyncHTTPProvider(get_blockchain_url()),
        modules={"eth": (AsyncEth,)},
        middlewares=[],
    )
    path = f"{os.path.dirname(os.path.abspath(__file__))}/dexes/assets/"
    with open(os.path.abspath(path + "erc20.abi")) as f:
        abi: str = json.load(f)
    address = Web3.toChecksumAddress("0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9")
    contract = w3.eth.contract(address=address, abi=abi)
    print(await contract.functions.name.call())

@MrNaif2018
Copy link

Yeah, just to confirm: using factories from eth object itself didn't work for me in my testing so I used AsyncContract.factory directly. So I can verify that the actual contract implementation works, but it's integration with eth object-not sure

@ChristianCoenen
Copy link

ChristianCoenen commented Mar 23, 2022

My bad regarding the None type exception. I forgot the parenthesis await contract.functions.name.call() -> await contract.functions.name().call().

Calls working now for me - so I'll continue migrating things over. Maybe I catch something when doing so.

pacrob pushed a commit to pacrob/web3.py that referenced this pull request Mar 31, 2022
* fill_transaction_defaults async for later use in build_transaction_for_function

* build_transaction in ContractFunction

* estimateGas  async in Contract and docs
pacrob pushed a commit to pacrob/web3.py that referenced this pull request Apr 13, 2022
* fill_transaction_defaults async for later use in build_transaction_for_function

* build_transaction in ContractFunction

* estimateGas  async in Contract and docs
pacrob pushed a commit to pacrob/web3.py that referenced this pull request Apr 23, 2022
* fill_transaction_defaults async for later use in build_transaction_for_function

* build_transaction in ContractFunction

* estimateGas  async in Contract and docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants