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:
DaniPopes 2022-11-30 22:20:22 +01:00 committed by GitHub
parent bbe53bfa2a
commit 2d793edc94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 92 deletions

View File

@ -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

View File

@ -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

View File

@ -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);