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
This commit is contained in:
parent
bbe53bfa2a
commit
2d793edc94
|
@ -31,8 +31,8 @@ mod multicall;
|
||||||
#[cfg(any(test, feature = "abigen"))]
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
pub use multicall::{
|
pub use multicall::{
|
||||||
Multicall, MulticallContract, MulticallError, MulticallVersion, MULTICALL_ADDRESS,
|
multicall_contract, Call, Multicall, MulticallContract, MulticallError, MulticallVersion,
|
||||||
MULTICALL_SUPPORTED_CHAIN_IDS,
|
MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This module exposes low lever builder structures which are only consumed by the
|
/// This module exposes low lever builder structures which are only consumed by the
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
|
use crate::{
|
||||||
|
call::{ContractCall, ContractError},
|
||||||
|
Lazy,
|
||||||
|
};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, Detokenize, Function, Token},
|
abi::{AbiDecode, Detokenize, Function, Token},
|
||||||
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256},
|
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256},
|
||||||
};
|
};
|
||||||
use ethers_providers::Middleware;
|
use ethers_providers::Middleware;
|
||||||
|
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
pub mod multicall_contract;
|
||||||
call::{ContractCall, ContractError},
|
|
||||||
Lazy,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod multicall_contract;
|
|
||||||
use multicall_contract::multicall_3::{
|
use multicall_contract::multicall_3::{
|
||||||
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
||||||
Result as MulticallResult,
|
Result as MulticallResult,
|
||||||
|
@ -263,11 +261,12 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[must_use = "Multicall does nothing unless you use `call` or `send`"]
|
#[must_use = "Multicall does nothing unless you use `call` or `send`"]
|
||||||
pub struct Multicall<M> {
|
pub struct Multicall<M> {
|
||||||
|
/// The Multicall contract interface.
|
||||||
|
pub contract: MulticallContract<M>,
|
||||||
version: MulticallVersion,
|
version: MulticallVersion,
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
calls: Vec<Call>,
|
calls: Vec<Call>,
|
||||||
contract: MulticallContract<M>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually implement Debug due to Middleware trait bounds.
|
// Manually implement Debug due to Middleware trait bounds.
|
||||||
|
@ -655,15 +654,20 @@ impl<M: Middleware> Multicall<M> {
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&return_data)
|
.zip(&return_data)
|
||||||
.map(|(call, bytes)| {
|
.map(|(call, bytes)| {
|
||||||
let mut tokens: Vec<Token> = call
|
// Always return an empty Bytes token for calls that return no data
|
||||||
|
if bytes.is_empty() {
|
||||||
|
Ok(Token::Bytes(Default::default()))
|
||||||
|
} else {
|
||||||
|
let mut tokens = call
|
||||||
.function
|
.function
|
||||||
.decode_output(bytes.as_ref())
|
.decode_output(bytes)
|
||||||
.map_err(ContractError::DecodingError)?;
|
.map_err(ContractError::DecodingError)?;
|
||||||
Ok(match tokens.len() {
|
Ok(match tokens.len() {
|
||||||
0 => Token::Tuple(vec![]),
|
0 => Token::Tuple(vec![]),
|
||||||
1 => tokens.remove(0),
|
1 => tokens.remove(0),
|
||||||
_ => Token::Tuple(tokens),
|
_ => Token::Tuple(tokens),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<Token>, M>>()?
|
.collect::<Result<Vec<Token>, M>>()?
|
||||||
}
|
}
|
||||||
|
@ -674,14 +678,17 @@ impl<M: Middleware> Multicall<M> {
|
||||||
let return_data = call.call().await?;
|
let return_data = call.call().await?;
|
||||||
self.calls
|
self.calls
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&return_data)
|
.zip(return_data.into_iter())
|
||||||
.map(|(call, res)| {
|
.map(|(call, res)| {
|
||||||
let ret = &res.return_data;
|
let bytes = &res.return_data;
|
||||||
let res_token: Token = if res.success {
|
// Always return an empty Bytes token for calls that return no data
|
||||||
|
let res_token: Token = if bytes.is_empty() {
|
||||||
|
Token::Bytes(Default::default())
|
||||||
|
} else if res.success {
|
||||||
// Decode using call.function
|
// Decode using call.function
|
||||||
let mut res_tokens = call
|
let mut res_tokens = call
|
||||||
.function
|
.function
|
||||||
.decode_output(ret)
|
.decode_output(bytes)
|
||||||
.map_err(ContractError::DecodingError)?;
|
.map_err(ContractError::DecodingError)?;
|
||||||
match res_tokens.len() {
|
match res_tokens.len() {
|
||||||
0 => Token::Tuple(vec![]),
|
0 => Token::Tuple(vec![]),
|
||||||
|
@ -702,16 +709,15 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode with "Error(string)" (0x08c379a0)
|
// Decode with "Error(string)" (0x08c379a0)
|
||||||
if ret.len() >= 4 && ret[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
if bytes.len() >= 4 && bytes[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
||||||
Token::String(
|
Token::String(
|
||||||
String::decode(&ret[4..]).map_err(ContractError::AbiError)?,
|
String::decode(&bytes[4..]).map_err(ContractError::AbiError)?,
|
||||||
)
|
)
|
||||||
} else if ret.is_empty() {
|
|
||||||
Token::String(String::new())
|
|
||||||
} else {
|
} else {
|
||||||
Token::Bytes(ret.to_vec())
|
Token::Bytes(bytes.to_vec())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// (bool, (...))
|
// (bool, (...))
|
||||||
Ok(Token::Tuple(vec![Token::Bool(res.success), res_token]))
|
Ok(Token::Tuple(vec![Token::Bool(res.success), res_token]))
|
||||||
})
|
})
|
||||||
|
@ -778,22 +784,15 @@ impl<M: Middleware> Multicall<M> {
|
||||||
// Map the calls vector into appropriate types for `aggregate` function
|
// Map the calls vector into appropriate types for `aggregate` function
|
||||||
let calls: Vec<Multicall1Call> = self
|
let calls: Vec<Multicall1Call> = self
|
||||||
.calls
|
.calls
|
||||||
.iter()
|
.clone()
|
||||||
.map(|call| Multicall1Call { target: call.target, call_data: call.data.clone() })
|
.into_iter()
|
||||||
|
.map(|call| Multicall1Call { target: call.target, call_data: call.data })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Construct the ContractCall for `aggregate` function to broadcast the transaction
|
// Construct the ContractCall for `aggregate` function to broadcast the transaction
|
||||||
let mut contract_call = self.contract.aggregate(calls);
|
let contract_call = self.contract.aggregate(calls);
|
||||||
|
|
||||||
if let Some(block) = self.block {
|
self.set_call_flags(contract_call)
|
||||||
contract_call = contract_call.block(block)
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.legacy {
|
|
||||||
contract_call = contract_call.legacy();
|
|
||||||
};
|
|
||||||
|
|
||||||
contract_call
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v2
|
/// v2
|
||||||
|
@ -802,28 +801,21 @@ impl<M: Middleware> Multicall<M> {
|
||||||
// Map the calls vector into appropriate types for `try_aggregate` function
|
// Map the calls vector into appropriate types for `try_aggregate` function
|
||||||
let calls: Vec<Multicall1Call> = self
|
let calls: Vec<Multicall1Call> = self
|
||||||
.calls
|
.calls
|
||||||
.iter()
|
.clone()
|
||||||
|
.into_iter()
|
||||||
.map(|call| {
|
.map(|call| {
|
||||||
// Allow entire call failure if at least one call is allowed to fail.
|
// Allow entire call failure if at least one call is allowed to fail.
|
||||||
// To avoid iterating multiple times, equivalent of:
|
// To avoid iterating multiple times, equivalent of:
|
||||||
// self.calls.iter().any(|call| call.allow_failure)
|
// self.calls.iter().any(|call| call.allow_failure)
|
||||||
allow_failure = allow_failure || call.allow_failure;
|
allow_failure |= call.allow_failure;
|
||||||
Multicall1Call { target: call.target, call_data: call.data.clone() }
|
Multicall1Call { target: call.target, call_data: call.data }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Construct the ContractCall for `try_aggregate` function to broadcast the transaction
|
// Construct the ContractCall for `try_aggregate` function to broadcast the transaction
|
||||||
let mut contract_call = self.contract.try_aggregate(!allow_failure, calls);
|
let contract_call = self.contract.try_aggregate(!allow_failure, calls);
|
||||||
|
|
||||||
if let Some(block) = self.block {
|
self.set_call_flags(contract_call)
|
||||||
contract_call = contract_call.block(block)
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.legacy {
|
|
||||||
contract_call = contract_call.legacy();
|
|
||||||
};
|
|
||||||
|
|
||||||
contract_call
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v3
|
/// v3
|
||||||
|
@ -831,26 +823,19 @@ impl<M: Middleware> Multicall<M> {
|
||||||
// Map the calls vector into appropriate types for `aggregate_3` function
|
// Map the calls vector into appropriate types for `aggregate_3` function
|
||||||
let calls: Vec<Multicall3Call> = self
|
let calls: Vec<Multicall3Call> = self
|
||||||
.calls
|
.calls
|
||||||
.iter()
|
.clone()
|
||||||
|
.into_iter()
|
||||||
.map(|call| Multicall3Call {
|
.map(|call| Multicall3Call {
|
||||||
target: call.target,
|
target: call.target,
|
||||||
call_data: call.data.clone(),
|
call_data: call.data,
|
||||||
allow_failure: call.allow_failure,
|
allow_failure: call.allow_failure,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Construct the ContractCall for `aggregate_3` function to broadcast the transaction
|
// Construct the ContractCall for `aggregate_3` function to broadcast the transaction
|
||||||
let mut contract_call = self.contract.aggregate_3(calls);
|
let contract_call = self.contract.aggregate_3(calls);
|
||||||
|
|
||||||
if let Some(block) = self.block {
|
self.set_call_flags(contract_call)
|
||||||
contract_call = contract_call.block(block)
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.legacy {
|
|
||||||
contract_call = contract_call.legacy();
|
|
||||||
};
|
|
||||||
|
|
||||||
contract_call
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v3 + values (only .send())
|
/// v3 + values (only .send())
|
||||||
|
@ -859,12 +844,13 @@ impl<M: Middleware> Multicall<M> {
|
||||||
let mut total_value = U256::zero();
|
let mut total_value = U256::zero();
|
||||||
let calls: Vec<Multicall3CallValue> = self
|
let calls: Vec<Multicall3CallValue> = self
|
||||||
.calls
|
.calls
|
||||||
.iter()
|
.clone()
|
||||||
|
.into_iter()
|
||||||
.map(|call| {
|
.map(|call| {
|
||||||
total_value += call.value;
|
total_value += call.value;
|
||||||
Multicall3CallValue {
|
Multicall3CallValue {
|
||||||
target: call.target,
|
target: call.target,
|
||||||
call_data: call.data.clone(),
|
call_data: call.data,
|
||||||
allow_failure: call.allow_failure,
|
allow_failure: call.allow_failure,
|
||||||
value: call.value,
|
value: call.value,
|
||||||
}
|
}
|
||||||
|
@ -877,17 +863,22 @@ impl<M: Middleware> Multicall<M> {
|
||||||
} else {
|
} else {
|
||||||
// Construct the ContractCall for `aggregate_3_value` function to broadcast the
|
// Construct the ContractCall for `aggregate_3_value` function to broadcast the
|
||||||
// transaction
|
// transaction
|
||||||
let mut contract_call = self.contract.aggregate_3_value(calls);
|
let contract_call = self.contract.aggregate_3_value(calls);
|
||||||
|
|
||||||
|
self.set_call_flags(contract_call).value(total_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the block and legacy flags on a [ContractCall] if they were set on Multicall.
|
||||||
|
fn set_call_flags<D: Detokenize>(&self, mut call: ContractCall<M, D>) -> ContractCall<M, D> {
|
||||||
if let Some(block) = self.block {
|
if let Some(block) = self.block {
|
||||||
contract_call = contract_call.block(block)
|
call = call.block(block);
|
||||||
};
|
}
|
||||||
|
|
||||||
if self.legacy {
|
if self.legacy {
|
||||||
contract_call = contract_call.legacy();
|
call = call.legacy();
|
||||||
};
|
}
|
||||||
|
|
||||||
contract_call.value(total_value)
|
call
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -694,22 +694,22 @@ mod eth_tests {
|
||||||
.clear_calls()
|
.clear_calls()
|
||||||
.add_call(empty_revert.clone(), true)
|
.add_call(empty_revert.clone(), true)
|
||||||
.add_call(empty_revert.clone(), true);
|
.add_call(empty_revert.clone(), true);
|
||||||
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
|
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
||||||
assert!(!res.0 .0);
|
assert!(!res.0 .0);
|
||||||
assert_eq!(res.0 .1, "");
|
assert_eq!(res.0 .1, Bytes::default());
|
||||||
|
|
||||||
// string revert
|
// string revert
|
||||||
let string_revert =
|
let string_revert =
|
||||||
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
||||||
multicall.clear_calls().add_call(string_revert, true).add_call(empty_revert.clone(), true);
|
multicall.clear_calls().add_call(string_revert, true).add_call(empty_revert.clone(), true);
|
||||||
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
|
let res: ((bool, String), (bool, Bytes)) = multicall.call().await.unwrap();
|
||||||
assert!(!res.0 .0);
|
assert!(!res.0 .0);
|
||||||
assert_eq!(res.0 .1, "String");
|
assert_eq!(res.0 .1, "String");
|
||||||
|
|
||||||
// custom error revert
|
// custom error revert
|
||||||
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
||||||
multicall.clear_calls().add_call(custom_error, true).add_call(empty_revert.clone(), true);
|
multicall.clear_calls().add_call(custom_error, true).add_call(empty_revert.clone(), true);
|
||||||
let res: ((bool, Bytes), (bool, String)) = multicall.call().await.unwrap();
|
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
||||||
let selector = &keccak256("CustomError()")[..4];
|
let selector = &keccak256("CustomError()")[..4];
|
||||||
assert!(!res.0 .0);
|
assert!(!res.0 .0);
|
||||||
assert_eq!(res.0 .1.len(), 4);
|
assert_eq!(res.0 .1.len(), 4);
|
||||||
|
@ -723,7 +723,7 @@ mod eth_tests {
|
||||||
.clear_calls()
|
.clear_calls()
|
||||||
.add_call(custom_error_with_data, true)
|
.add_call(custom_error_with_data, true)
|
||||||
.add_call(empty_revert.clone(), true);
|
.add_call(empty_revert.clone(), true);
|
||||||
let res: ((bool, Bytes), (bool, String)) = multicall.call().await.unwrap();
|
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
||||||
let selector = &keccak256("CustomErrorWithData(string)")[..4];
|
let selector = &keccak256("CustomErrorWithData(string)")[..4];
|
||||||
assert!(!res.0 .0);
|
assert!(!res.0 .0);
|
||||||
assert_eq!(&res.0 .1[..4], selector);
|
assert_eq!(&res.0 .1[..4], selector);
|
||||||
|
|
Loading…
Reference in New Issue