-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Fix initialization of Contract function and events #3540
Fix initialization of Contract function and events #3540
Conversation
- ``web3.ens`` invokes the ``@property`` getter on ``ens``. This getter sets ``self._ens`` on the ``web3`` object if it is empty. We do not need to do invoke this getter until ``ens`` is actually called. Instead, use the pointer to ``self._ens`` directly to avoid overhead when the majority of uses may not even be using ENS.
- When initializing a contract, we create the ``ContractFunction`` objects for each function and assign them to the ``contract.functions`` attribute. Then we instantiate the ``contract.caller`` and inside the ``ContractCaller`` init, we re-instantiate each contract function. Pass these around instead which will prevent so many calls for validation. They are already built, so there is no reason to build them again.
d716d52
to
926a200
Compare
926a200
to
bf62ecf
Compare
- Instead of re-building the ``ContractFunction`` object, use the already existing ``self`` object reference.
…us errors. Add fixture contract with ambiguous functions and arguments.
df01e5e
to
a8e5346
Compare
ccff69b
to
4a0975a
Compare
40cf65c
to
170025d
Compare
170025d
to
7588418
Compare
7588418
to
67ae66c
Compare
… arguments. Initialize ContractFunction and ContractEvent with `signature`. Create ContractFunctions with properties for the function name and also private properties with each function signature Create ContractEvents with properties for the event name and also private properties with each event signature
67ae66c
to
16a5c2e
Compare
@fselmo couldnt add you as a reviewer of your own PR, but I've got all changes pushed up. I appreciate your feedback! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't do a terribly deep dive here, but this looks good on a high level! I left a couple nits, but the biggest things I'm wondering about are:
- what does the benchmarking say here; does this make a meaningful impact? (I assume yes, but would be nice to know the actual numbers)
- Are you pretty confident in the test coverage?
== 0 | ||
) | ||
assert ambiguous_function_contract.functions.isValidSignature().call() == "valid" | ||
assert ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We chatted about this but I'll add it here so we can all think on it. I think in most cases this can be pretty harmless, to make a decision that you are calling the (bytes,bytes)
version of the method because the first argument is not encodable as bytes32
.
Let's say I thought I did some good math and I ended up padding the value I wanted to pass in here to where it was 33 bytes instead of 32. Then, the method I'm actually calling ends up being (bytes,bytes)
with no exception or warning but I thought that it was (bytes32,bytes)
. I understand that had I actually called the (bytes32,bytes)
, it would've raised (as per the test below)... but since I didn't I never understood the whole flow here.
I can see this being used as an exploit at the worst case scenario, but also leading to undesired behavior at times on a level below that.
I would prefer a default API that gives me full control and confidence that I'm doing the right thing. If I got an exception here that asked me to explicitly get my function by signature before calling it, I think that would give me more confidence that I'm dealing with exactly the methods I want to deal with.
Some lingering questions for me are. How would we draw these lines? We don't have to be as strict as same # of args in function with same name... but maybe that's a good line to draw?
I also might be OK keeping it like this if we document this well enough. But not a whole lot of people read the docs thoroughly before using the library, so this wouldn't address the issue to a lot of users as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talking more on this, you're right that we ideally wouldn't want to raise on isValidSignature(string)
vs isValidSignature(bytes)
. We should be able to discern between the two in that case.
I think I'm OK with documenting this well enough and giving users the ability to call the function by the signature should empower them with the right approach for their workflow. I would want to be explicit, whereas another user might want the flexibility of knowing they are calling (bytes,bytes)
simply because their first arg is not length 32.
After profiling the contract code, I found vast improvements from the latest changes here. I wrote a script that only initialized 10 contracts, then called cPython to profile the calls with Here are my findings running my script:
|
9f18da0
to
c82fdb5
Compare
I can't approve my own PR but this looks good to me once we address the outstanding comments 👍🏼 |
@kclowes we'll add benchmarking tests for contracts at a later time, but the coverage is good. Many of the test cases we already had that broke were fixed along the way. Everything has been addressed up to this point so I'm confident in this being released now! Let me know if there's anything else you'd like to check before I merge. |
a44ace3
to
d9b951f
Compare
- Pass the already built contract functions in the contract init when initializing the contract caller for ``AsyncContract``. - Add reasonable init time test for ``Contract`` and ``AsyncContract`` so that we know when code deviates from the expected performance.
d9b951f
to
2c978d1
Compare
2c978d1
to
89a0fc4
Compare
89a0fc4
to
eb99edc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One note on docs formatting intention, otherwise lgtm!
Include comments for overloaded functions with similar arguments.
eb99edc
to
f20abf7
Compare
Closes #3553
What was wrong?
w3.ens
), set all initializing vars tow3._ens
which is the reference thatw3.ens
looks for. This leaves the check for an emptyw3._ens
followed by subsequent instantiation for whenw3.ens
is called directly by the user, not for any internal setup.Contract
init, as well as for theContractCaller
. The caller is instantiated in theContract
init when we setself.caller = ContractCaller(...)
. Pass along the already builtContractFunctions
to theContractCaller
so that it doesn't have to re-build them.How was it fixed?
w3._ens
rather thanw3.ens
(which initializes an ENS instance if it is empty).contract_functions: ContractFunctions
to theContractCaller
class so that we can pass the functions around instead of rebuilding them everywhere they are needed.Todo:
Cute Animal Picture