-
Notifications
You must be signed in to change notification settings - Fork 553
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
feat: Multicall context manager #1125
feat: Multicall context manager #1125
Conversation
3073f09
to
d5ed965
Compare
Looking into the CI errors :/
|
d6a73a2
to
d15ea6e
Compare
This is a nice evolution of ideas presented in previous pull requests. I'd prefer it would be called simply |
Yeah thinking on it some more I prefer that as well. 🤔 |
001bd0a
to
75cc75e
Compare
|
9759082
to
723fce1
Compare
723fce1
to
1ae603b
Compare
This is very cool! I'm not a huge fan of requiring the with multicall:
# this uses multicall
Foo.bar(1, 2)
# this bypasses
Foo.bar.call(1, 2) Perhaps instead of relying on the tx dict to pass the multicall instance, we can store it as a temporary private member of I also agree with @banteg that just |
1ae603b
to
c273da4
Compare
Taken directly from the makerdao/multicall repository. [Permalink](https://github.com/makerdao/multicall/blob/d2f67ac4cbcaff5cb654aebd28a27a331163d2cb/src/Multicall2.sol)
The multicall2 abi added has the stateMutability for nonpayable functions changed to view.
Update to include solidity contracts from the brownie/data/contracts directory.
Now importable and used as brownie.multicall()
Testing the deploy classmethod. Testing an error is raised when trying to use multicall contract which didn't exist at specified block number.
Previously, if the block_identifier was not specified and the pending calls queue was flushed, any future pending calls would come from a different block. To handle this, we now on init hardcode the block identifier to call from if not specified. Also handled is if the block identifier is specified in a dev network, and the address is not supplied, the user needs to deploy multicall2 first. (we raise a ContractNotFound error, since the contract was not found at user specified block height)
I guess this means once instantiated and used, __call__ doesn't work as expected anymore and is fully monkeypatched ... I suspect adding in a hook into the ContractCall class would be a better alternative than this monkeypatching, and less prone to error in the future. Should look into refactoring completely.
a0c1262
to
1dd0722
Compare
1dd0722
to
e85bf9f
Compare
brownie/network/multicall.py
Outdated
This makes constant contract calls look more like transactions since we require | ||
users to specify a dictionary as the last argument with the from field | ||
being the multicall2 instance being used.""" | ||
from threading import get_ident |
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.
Is this import statement necessary?
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.
Ehh it is gross, what I can do is move the import over to the contract.py file then we won't have to import in the function
What I did
Added context manager enabling the use of Multicall to batch/aggregate contract calls.
This is brownie sugar.
Related issue: #1011
How I did it
tl;dr - I did some voodoo
I monkeypatched the runtime bytecode of the
ContractCall.__call__
, and substituted it's code object for a proxy function, which essentially intercepts the calls while the context manager is active. For constant contract calls which have a dictionary last argument, we take the call object along with the arguments, and place it in a buffer. For standard calls we let them pass through.So essentially some runtime wrapping.
There is also two layers of proxy objects used. The first layer is a simple proxy with
wrapt.ObjectProxy
, which simply allows us to return to the user a proxy object which we can update later after making the multicall aggregate call. The second layer uses thelazy_object_proxy.Proxy
to allow for lazy execution. This allows for some magic, since the next time this proxy object is used (so the next usage after assignment, or placement), it automatically calls a flush function. The flush function then flushes the queue of pending calls and returns proxy object.How to verify it
Run the tests, I don't think this is exhaustive, and I definitely should have a couple more sets of eyes on this.
Usage
Users won't feel too lost with the usage, the syntax is similar to a transaction and merely requires the last argument to a contract call to be a dictionary with a single
from
key with the value being the multicall2 instance returned when opening the context manager.Standard calls are passed through as regular when a dictionary isn't provided.
Example usage:
One downside is that the lazy proxy objects don't look too nice, but they workChecklist