Skip to content
This repository was archived by the owner on Oct 19, 2024. It is now read-only.

Commit 2d793ed

Browse files
authored
fix(contract): multicall decode error (#1907)
* wip * use empty bytes for reverts * minor improvements * fix test * use is_empty * docs * clippy * fix: rust-analyzer bug * revert rename
1 parent bbe53bf commit 2d793ed

File tree

4 files changed

+84
-90
lines changed

4 files changed

+84
-90
lines changed

Diff for: ethers-contract/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ mod multicall;
3131
#[cfg(any(test, feature = "abigen"))]
3232
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
3333
pub use multicall::{
34-
Multicall, MulticallContract, MulticallError, MulticallVersion, MULTICALL_ADDRESS,
35-
MULTICALL_SUPPORTED_CHAIN_IDS,
34+
multicall_contract, Call, Multicall, MulticallContract, MulticallError, MulticallVersion,
35+
MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS,
3636
};
3737

3838
/// This module exposes low lever builder structures which are only consumed by the

Diff for: ethers-contract/src/multicall/mod.rs

+64-73
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1+
use crate::{
2+
call::{ContractCall, ContractError},
3+
Lazy,
4+
};
15
use ethers_core::{
26
abi::{AbiDecode, Detokenize, Function, Token},
37
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256},
48
};
59
use ethers_providers::Middleware;
6-
710
use std::{convert::TryFrom, sync::Arc};
811

9-
use crate::{
10-
call::{ContractCall, ContractError},
11-
Lazy,
12-
};
13-
14-
mod multicall_contract;
12+
pub mod multicall_contract;
1513
use multicall_contract::multicall_3::{
1614
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
1715
Result as MulticallResult,
@@ -263,11 +261,12 @@ impl TryFrom<u8> for MulticallVersion {
263261
#[derive(Clone)]
264262
#[must_use = "Multicall does nothing unless you use `call` or `send`"]
265263
pub struct Multicall<M> {
264+
/// The Multicall contract interface.
265+
pub contract: MulticallContract<M>,
266266
version: MulticallVersion,
267267
legacy: bool,
268268
block: Option<BlockNumber>,
269269
calls: Vec<Call>,
270-
contract: MulticallContract<M>,
271270
}
272271

273272
// Manually implement Debug due to Middleware trait bounds.
@@ -655,15 +654,20 @@ impl<M: Middleware> Multicall<M> {
655654
.iter()
656655
.zip(&return_data)
657656
.map(|(call, bytes)| {
658-
let mut tokens: Vec<Token> = call
659-
.function
660-
.decode_output(bytes.as_ref())
661-
.map_err(ContractError::DecodingError)?;
662-
Ok(match tokens.len() {
663-
0 => Token::Tuple(vec![]),
664-
1 => tokens.remove(0),
665-
_ => Token::Tuple(tokens),
666-
})
657+
// Always return an empty Bytes token for calls that return no data
658+
if bytes.is_empty() {
659+
Ok(Token::Bytes(Default::default()))
660+
} else {
661+
let mut tokens = call
662+
.function
663+
.decode_output(bytes)
664+
.map_err(ContractError::DecodingError)?;
665+
Ok(match tokens.len() {
666+
0 => Token::Tuple(vec![]),
667+
1 => tokens.remove(0),
668+
_ => Token::Tuple(tokens),
669+
})
670+
}
667671
})
668672
.collect::<Result<Vec<Token>, M>>()?
669673
}
@@ -674,14 +678,17 @@ impl<M: Middleware> Multicall<M> {
674678
let return_data = call.call().await?;
675679
self.calls
676680
.iter()
677-
.zip(&return_data)
681+
.zip(return_data.into_iter())
678682
.map(|(call, res)| {
679-
let ret = &res.return_data;
680-
let res_token: Token = if res.success {
683+
let bytes = &res.return_data;
684+
// Always return an empty Bytes token for calls that return no data
685+
let res_token: Token = if bytes.is_empty() {
686+
Token::Bytes(Default::default())
687+
} else if res.success {
681688
// Decode using call.function
682689
let mut res_tokens = call
683690
.function
684-
.decode_output(ret)
691+
.decode_output(bytes)
685692
.map_err(ContractError::DecodingError)?;
686693
match res_tokens.len() {
687694
0 => Token::Tuple(vec![]),
@@ -702,16 +709,15 @@ impl<M: Middleware> Multicall<M> {
702709
}
703710

704711
// Decode with "Error(string)" (0x08c379a0)
705-
if ret.len() >= 4 && ret[..4] == [0x08, 0xc3, 0x79, 0xa0] {
712+
if bytes.len() >= 4 && bytes[..4] == [0x08, 0xc3, 0x79, 0xa0] {
706713
Token::String(
707-
String::decode(&ret[4..]).map_err(ContractError::AbiError)?,
714+
String::decode(&bytes[4..]).map_err(ContractError::AbiError)?,
708715
)
709-
} else if ret.is_empty() {
710-
Token::String(String::new())
711716
} else {
712-
Token::Bytes(ret.to_vec())
717+
Token::Bytes(bytes.to_vec())
713718
}
714719
};
720+
715721
// (bool, (...))
716722
Ok(Token::Tuple(vec![Token::Bool(res.success), res_token]))
717723
})
@@ -778,22 +784,15 @@ impl<M: Middleware> Multicall<M> {
778784
// Map the calls vector into appropriate types for `aggregate` function
779785
let calls: Vec<Multicall1Call> = self
780786
.calls
781-
.iter()
782-
.map(|call| Multicall1Call { target: call.target, call_data: call.data.clone() })
787+
.clone()
788+
.into_iter()
789+
.map(|call| Multicall1Call { target: call.target, call_data: call.data })
783790
.collect();
784791

785792
// Construct the ContractCall for `aggregate` function to broadcast the transaction
786-
let mut contract_call = self.contract.aggregate(calls);
787-
788-
if let Some(block) = self.block {
789-
contract_call = contract_call.block(block)
790-
};
791-
792-
if self.legacy {
793-
contract_call = contract_call.legacy();
794-
};
793+
let contract_call = self.contract.aggregate(calls);
795794

796-
contract_call
795+
self.set_call_flags(contract_call)
797796
}
798797

799798
/// v2
@@ -802,55 +801,41 @@ impl<M: Middleware> Multicall<M> {
802801
// Map the calls vector into appropriate types for `try_aggregate` function
803802
let calls: Vec<Multicall1Call> = self
804803
.calls
805-
.iter()
804+
.clone()
805+
.into_iter()
806806
.map(|call| {
807807
// Allow entire call failure if at least one call is allowed to fail.
808808
// To avoid iterating multiple times, equivalent of:
809809
// self.calls.iter().any(|call| call.allow_failure)
810-
allow_failure = allow_failure || call.allow_failure;
811-
Multicall1Call { target: call.target, call_data: call.data.clone() }
810+
allow_failure |= call.allow_failure;
811+
Multicall1Call { target: call.target, call_data: call.data }
812812
})
813813
.collect();
814814

815815
// Construct the ContractCall for `try_aggregate` function to broadcast the transaction
816-
let mut contract_call = self.contract.try_aggregate(!allow_failure, calls);
817-
818-
if let Some(block) = self.block {
819-
contract_call = contract_call.block(block)
820-
};
816+
let contract_call = self.contract.try_aggregate(!allow_failure, calls);
821817

822-
if self.legacy {
823-
contract_call = contract_call.legacy();
824-
};
825-
826-
contract_call
818+
self.set_call_flags(contract_call)
827819
}
828820

829821
/// v3
830822
fn as_aggregate_3(&self) -> ContractCall<M, Vec<MulticallResult>> {
831823
// Map the calls vector into appropriate types for `aggregate_3` function
832824
let calls: Vec<Multicall3Call> = self
833825
.calls
834-
.iter()
826+
.clone()
827+
.into_iter()
835828
.map(|call| Multicall3Call {
836829
target: call.target,
837-
call_data: call.data.clone(),
830+
call_data: call.data,
838831
allow_failure: call.allow_failure,
839832
})
840833
.collect();
841834

842835
// Construct the ContractCall for `aggregate_3` function to broadcast the transaction
843-
let mut contract_call = self.contract.aggregate_3(calls);
844-
845-
if let Some(block) = self.block {
846-
contract_call = contract_call.block(block)
847-
};
836+
let contract_call = self.contract.aggregate_3(calls);
848837

849-
if self.legacy {
850-
contract_call = contract_call.legacy();
851-
};
852-
853-
contract_call
838+
self.set_call_flags(contract_call)
854839
}
855840

856841
/// v3 + values (only .send())
@@ -859,12 +844,13 @@ impl<M: Middleware> Multicall<M> {
859844
let mut total_value = U256::zero();
860845
let calls: Vec<Multicall3CallValue> = self
861846
.calls
862-
.iter()
847+
.clone()
848+
.into_iter()
863849
.map(|call| {
864850
total_value += call.value;
865851
Multicall3CallValue {
866852
target: call.target,
867-
call_data: call.data.clone(),
853+
call_data: call.data,
868854
allow_failure: call.allow_failure,
869855
value: call.value,
870856
}
@@ -877,17 +863,22 @@ impl<M: Middleware> Multicall<M> {
877863
} else {
878864
// Construct the ContractCall for `aggregate_3_value` function to broadcast the
879865
// transaction
880-
let mut contract_call = self.contract.aggregate_3_value(calls);
866+
let contract_call = self.contract.aggregate_3_value(calls);
881867

882-
if let Some(block) = self.block {
883-
contract_call = contract_call.block(block)
884-
};
868+
self.set_call_flags(contract_call).value(total_value)
869+
}
870+
}
885871

886-
if self.legacy {
887-
contract_call = contract_call.legacy();
888-
};
872+
/// Sets the block and legacy flags on a [ContractCall] if they were set on Multicall.
873+
fn set_call_flags<D: Detokenize>(&self, mut call: ContractCall<M, D>) -> ContractCall<M, D> {
874+
if let Some(block) = self.block {
875+
call = call.block(block);
876+
}
889877

890-
contract_call.value(total_value)
878+
if self.legacy {
879+
call = call.legacy();
891880
}
881+
882+
call
892883
}
893884
}

Diff for: ethers-contract/src/multicall/multicall_contract.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@ pub mod multicall_3 {
44
#![allow(dead_code)]
55
#![allow(clippy::type_complexity)]
66
#![allow(unused_imports)]
7-
/// Some macros may expand into using `ethers_contract` instead of `crate`.
8-
mod ethers_contract {
9-
pub use crate::*;
10-
}
117

128
// This is a hack to guarantee all ethers-derive macros can find the types.
13-
// See [`ethers_core::macros::determine_ethers_crates`]
9+
// See [`ethers_core::macros::determine_ethers_crates`].
1410
#[doc(hidden)]
1511
mod ethers {
16-
pub mod core {
17-
pub use ethers_core::*;
18-
}
1912
pub mod contract {
2013
pub use crate::*;
2114
}
15+
pub mod core {
16+
pub use ethers_core::*;
17+
}
2218
pub mod providers {
2319
pub use ethers_providers::*;
2420
}
21+
pub mod types {
22+
pub use ethers_core::types::*;
23+
}
24+
}
25+
#[doc(hidden)]
26+
mod ethers_contract {
27+
pub use crate::*;
2528
}
2629

2730
use self::ethers_contract::{
@@ -30,12 +33,12 @@ pub mod multicall_3 {
3033
};
3134
use ethers_core::{
3235
abi::{Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
33-
types::*,
36+
types::{Address, Bytes, U256},
3437
};
3538
use ethers_providers::Middleware;
39+
use std::sync::Arc;
3640

3741
#[doc = "Multicall3 was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"]
38-
use std::sync::Arc;
3942
# [rustfmt :: skip] const __ABI : & str = "[{\"type\":\"function\",\"name\":\"aggregate\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"aggregate3\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call3[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"aggregate3Value\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call3Value[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bool\"},{\"type\":\"uint256\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"blockAndAggregate\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"getBasefee\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"basefee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBlockHash\",\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBlockNumber\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getChainId\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainid\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockCoinbase\",\"inputs\":[],\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockDifficulty\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockGasLimit\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockTimestamp\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEthBalance\",\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getLastBlockHash\",\"inputs\":[],\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"tryAggregate\",\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"tryBlockAndAggregate\",\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"}]" ;
4043
#[doc = r" The parsed JSON-ABI of the contract."]
4144
pub static MULTICALL3_ABI: ethers_contract::Lazy<ethers_core::abi::Abi> =

Diff for: ethers-contract/tests/it/contract.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -694,22 +694,22 @@ mod eth_tests {
694694
.clear_calls()
695695
.add_call(empty_revert.clone(), true)
696696
.add_call(empty_revert.clone(), true);
697-
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
697+
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
698698
assert!(!res.0 .0);
699-
assert_eq!(res.0 .1, "");
699+
assert_eq!(res.0 .1, Bytes::default());
700700

701701
// string revert
702702
let string_revert =
703703
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
704704
multicall.clear_calls().add_call(string_revert, true).add_call(empty_revert.clone(), true);
705-
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
705+
let res: ((bool, String), (bool, Bytes)) = multicall.call().await.unwrap();
706706
assert!(!res.0 .0);
707707
assert_eq!(res.0 .1, "String");
708708

709709
// custom error revert
710710
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
711711
multicall.clear_calls().add_call(custom_error, true).add_call(empty_revert.clone(), true);
712-
let res: ((bool, Bytes), (bool, String)) = multicall.call().await.unwrap();
712+
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
713713
let selector = &keccak256("CustomError()")[..4];
714714
assert!(!res.0 .0);
715715
assert_eq!(res.0 .1.len(), 4);
@@ -723,7 +723,7 @@ mod eth_tests {
723723
.clear_calls()
724724
.add_call(custom_error_with_data, true)
725725
.add_call(empty_revert.clone(), true);
726-
let res: ((bool, Bytes), (bool, String)) = multicall.call().await.unwrap();
726+
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
727727
let selector = &keccak256("CustomErrorWithData(string)")[..4];
728728
assert!(!res.0 .0);
729729
assert_eq!(&res.0 .1[..4], selector);

0 commit comments

Comments
 (0)