-
Notifications
You must be signed in to change notification settings - Fork 795
Implement Multicall functionality for batched calls #43
Conversation
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.
Functionally looks good, we're missing docs + a test where there are state mutating functions (in addition to the current one which nicely tests the call
s) - you already have your checklist above, but just re-iterating
I also think that since add_call
already returns Self
, we can remove the add_calls
function and just encourage people to go with the builder pattern.
I think that instead of returning a new error in the MultiCall
, it's fine to panic
if an address is not set and if the address is not found in the address book (just add it with a # Panics
doc).
Added another utility method |
1dd2e86
to
b3debd0
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.
LGTM!
Motivation
This PR targets #36
The motivation is to be able to read multiple values from the blockchain state (
call
s may be made to different smart contracts). This reduces the number of round trips and hence saves time.Solution
The solution has been inspired by the one prescribed in the #36 issue. We aggregate the calldata in a
Multicall
struct instance and then call Maker's Multicall contract, which is deployed on multiple Ethereum networks.Using the
Multicall
API would look something, like implemented in the test:Pending tasks
Multicall
module, as it is also exportedErr
type I should return. I have simply used theContractError::ConstructorError
type. This should of course be changed, but not sure if I should add another error type to theContractError
enums? Or have a separate enum forMulticall
?Abigen
logicAt present, for a smart contract function with a single argument, the generated binding is:
This does make sense for multi-argument function, where a tuple is required. But in the case of a single argument, I think we should prefer something like this:
Otherwise, the arguments supplied to the function call cannot be encoded and the
ethabi::Function::encode_input
throws anInvalidData
error.Ethers-rs
supports at the most 16 elements in a tuple for tokenization/detokenization. We must take note of that and either:ethers-core/src/tokens.rs
Multicall.calls
vector (this has to be done either way)So that we don't risk a
Detokenization
error.ethers-rs
's tokenization/detokenization logicThis sub-task is mainly concerned with the traits
Tokenizable
,TokenizableItem
and the wayDetokenize
is implemented for types that do implementTokenizable
. Please note the pre-processing and post-processing done to the tokens. This is done because:ethabi
tokenizes a tuple return value as a vector of those individual tokensSo, for a function returning
(string, address)
,ethabi
would tokenize it as:But
ethers-rs
denotes them as aToken::Tuple
, hence:We can adapt to their tokenization approach (it already was like that before, when nested tuples wouldn't be possible to detokenize). But we will have to then either give up on the nested tokenization and also on tokenization of a vector of tuples. If not, we would have to customize
ethabi
to suit our needs, or continue with thispre
andpost-processing
approach I have taken (To be fair, I think this wouldn't be THAT bad).Abigen
logicAt the moment, a contract function that is not
view
returns aH256
for the transaction hash, as we expect a user tosend
a transaction to that function. But even acall
could be made to such a function, and it could return some data. In order to address this, as suggested by @gakonst himself, we would need a struct similar to the NameOrAddress, and calling itHashOrData<T>
. This should be used as theD: Detokenize
type for such a function, as it could return aH256
onContractCall.send()
orT
onContractCall.call()
.