-
Notifications
You must be signed in to change notification settings - Fork 554
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
Multicall context manager #1011
Comments
This is an amazing idea! A few caveats:
|
I've been thinking about this over the past couple days and wanted to float an idea. Would we definitely want it to automatically resolve calls (i.e know to evaluate One possible design alternative, just to be exhaustive: # (A) This works
with multicall:
a = foo.bar()
b = foo.barfoo()
# (B) This raises an error
with multicall:
a = foo.bar()
b = foo.barfoo()
c = bar.foobar(a)
# (C) This is how we'd want to write (B)
with multicall:
a = foo.bar()
b = foo.barfoo()
with multicall:
c = bar.foobar(a)
d = bar.otherbar() (C) Has the advantage of being more explicit, but I agree, is sort of annoying to have to think about re-opening the context manager. |
I agree that the automatic execution mid-context manager is a bit magical, but when I think about how I personally would feel writing this code, it'd drive me mad having to endlessly close and reopen it. A middleground here might be a kwarg to enable or disable this behavior? |
For sure. I think a clean implementation would have at least two pieces: a private function to commit a single transaction block (like scenario A above), and then something higher level (probably public) that calls that function every time (1) the multicall needs to be executed, and (2) when the context manager Perhaps something like this: def _exec_multicall(*args, **kwargs):
# used to run a series of calls and will error out
@contextmanager
def multicall(*args, **kwargs):
# this is the actual context manager that's exposed, it has some logic and uses `_exec_multicall` for batches. We could start with the simplest thing, which would be the context manager as initially proposed, then we could see how it feels and go from there. How does that sound? |
Battle of the Mulitcalls !!! |
Implemented in #1125 |
Overview
Brownie should offer first-class support for multicall via a context manager:
Specification
While the context manager is open, a middleware intercepts all
eth_call
requests and returns objects which represent the result of the call once it completes. When the context manager closes, or there is an attempt to use one of the pending call objects, a single multicall is made to get the actual values.In the above example
a
andb
would end up joined as a single multicall that executes whena
is needed forc
. Thenc
andd
would happen as a multicall when the context manager exits.We can bundle the
MultiCall2
implementation as used by yearn, which uses try/catch to prevent calls from failing. Attempting to interact with object for the specific result that failed in the multicall should raise aValueError
. (Or possibly the object simply returnsNone
? Maybe this behavior is determined via a kwarg in the context manager?)It is important to consider thread safety during the implementation. The context manager must introspect to be aware of which thread it is running on, and not also batch
eth_call
operations coming from unrelated threads.To ensure consistent behaviour across network, we can use Geth's state override feature to simulate the existence of a multicall contract for the purposes of the call. This is badass in so many ways I'm not even sure where to begin 😈 For dev networks where this isn't possible, deploy multicall silently the first time it's required.
The text was updated successfully, but these errors were encountered: