#![allow(clippy::return_self_not_must_use)]
use crate::{error::ContractRevert, EthError};
use super::base::{decode_function_data, AbiError};
use ethers_core::{
abi::{Detokenize, Function, InvalidOutputType},
types::{
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256,
},
};
use ethers_providers::{
call_raw::{CallBuilder, RawCall},
JsonRpcError, Middleware, MiddlewareError, PendingTransaction, ProviderError,
};
use std::{
borrow::Borrow,
fmt::Debug,
future::{Future, IntoFuture},
marker::PhantomData,
pin::Pin,
};
use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
pub enum ContractError<M: Middleware> {
#[error(transparent)]
DecodingError(#[from] ethers_core::abi::Error),
#[error(transparent)]
AbiError(#[from] AbiError),
#[error(transparent)]
DetokenizationError(#[from] InvalidOutputType),
#[error("{e}")]
MiddlewareError {
e: M::Error,
},
#[error("{e}")]
ProviderError {
e: ProviderError,
},
#[error("Contract call reverted with data: {0}")]
Revert(Bytes),
#[error("constructor is not defined in the ABI")]
ConstructorError,
#[error("Contract was not deployed")]
ContractNotDeployed,
}
impl<M: Middleware> ContractError<M> {
pub fn as_revert(&self) -> Option<&Bytes> {
match self {
ContractError::Revert(data) => Some(data),
_ => None,
}
}
pub fn is_revert(&self) -> bool {
matches!(self, ContractError::Revert(_))
}
pub fn decode_revert<Err: EthError>(&self) -> Option<Err> {
self.as_revert().and_then(|data| Err::decode_with_selector(data))
}
pub fn decode_contract_revert<Err: ContractRevert>(&self) -> Option<Err> {
self.as_revert().and_then(|data| Err::decode_with_selector(data))
}
pub fn from_middleware_error(e: M::Error) -> Self {
if let Some(data) = e.as_error_response().and_then(JsonRpcError::as_revert_data) {
ContractError::Revert(data)
} else {
ContractError::MiddlewareError { e }
}
}
pub fn as_middleware_error(&self) -> Option<&M::Error> {
match self {
ContractError::MiddlewareError { e } => Some(e),
_ => None,
}
}
pub fn is_middleware_error(&self) -> bool {
matches!(self, ContractError::MiddlewareError { .. })
}
pub fn as_provider_error(&self) -> Option<&ProviderError> {
match self {
ContractError::ProviderError { e } => Some(e),
_ => None,
}
}
pub fn is_provider_error(&self) -> bool {
matches!(self, ContractError::ProviderError { .. })
}
}
impl<M: Middleware> From<ProviderError> for ContractError<M> {
fn from(e: ProviderError) -> Self {
if let Some(data) = e.as_error_response().and_then(JsonRpcError::as_revert_data) {
ContractError::Revert(data)
} else {
ContractError::ProviderError { e }
}
}
}
pub type ContractCall<M, D> = FunctionCall<std::sync::Arc<M>, M, D>;
#[derive(Debug)]
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
pub struct FunctionCall<B, M, D> {
pub tx: TypedTransaction,
pub function: Function,
pub block: Option<BlockId>,
pub(crate) client: B,
pub(crate) datatype: PhantomData<D>,
pub(crate) _m: PhantomData<M>,
}
impl<B, M, D> Clone for FunctionCall<B, M, D>
where
B: Clone,
{
fn clone(&self) -> Self {
FunctionCall {
tx: self.tx.clone(),
function: self.function.clone(),
block: self.block,
client: self.client.clone(),
datatype: self.datatype,
_m: self._m,
}
}
}
impl<B, M, D> FunctionCall<B, M, D>
where
B: Borrow<M>,
D: Detokenize,
{
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
self.tx.set_from(from.into());
self
}
pub fn legacy(mut self) -> Self {
self.tx = match self.tx {
TypedTransaction::Eip1559(inner) => {
let tx: TransactionRequest = inner.into();
TypedTransaction::Legacy(tx)
}
other => other,
};
self
}
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
self.tx.set_gas(gas);
self
}
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
self.tx.set_gas_price(gas_price);
self
}
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
self.tx.set_value(value);
self
}
pub fn block<T: Into<BlockId>>(mut self, block: T) -> Self {
self.block = Some(block.into());
self
}
pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
self.tx.set_nonce(nonce);
self
}
}
impl<B, M, D> FunctionCall<B, M, D>
where
B: Borrow<M>,
M: Middleware,
D: Detokenize,
{
pub fn calldata(&self) -> Option<Bytes> {
self.tx.data().cloned()
}
pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
self.client
.borrow()
.estimate_gas(&self.tx, self.block)
.await
.map_err(ContractError::from_middleware_error)
}
pub async fn call(&self) -> Result<D, ContractError<M>> {
let bytes = self
.client
.borrow()
.call(&self.tx, self.block)
.await
.map_err(ContractError::from_middleware_error)?;
let data = decode_function_data(&self.function, &bytes, false)?;
Ok(data)
}
pub fn call_raw(
&self,
) -> impl RawCall<'_> + Future<Output = Result<D, ContractError<M>>> + Debug {
let call = self.call_raw_bytes();
call.map(move |res: Result<Bytes, ProviderError>| {
let bytes = res?;
decode_function_data(&self.function, &bytes, false).map_err(From::from)
})
}
pub fn call_raw_bytes(&self) -> CallBuilder<'_, M::Provider> {
let call = self.client.borrow().provider().call_raw(&self.tx);
if let Some(block) = self.block {
call.block(block)
} else {
call
}
}
pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> {
self.client
.borrow()
.send_transaction(self.tx.clone(), self.block)
.await
.map_err(ContractError::from_middleware_error)
}
}
impl<B, M, D> IntoFuture for FunctionCall<B, M, D>
where
Self: 'static,
B: Borrow<M> + Send + Sync,
M: Middleware,
D: Detokenize + Send + Sync,
{
type Output = Result<D, ContractError<M>>;
#[cfg(target_arch = "wasm32")]
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
#[cfg(not(target_arch = "wasm32"))]
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
#[allow(clippy::redundant_async_block)]
Box::pin(async move { self.call().await })
}
}