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::{Middleware, PendingTransaction, ProviderError}; use std::borrow::Cow; use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use crate::{AbiDecode, AbiEncode}; use ethers_core::abi::{Tokenizable, Tokenize}; use ethers_core::types::Selector; use ethers_core::utils::id; use thiserror::Error as ThisError; /// A helper trait for types that represent all call input parameters of a specific function pub trait EthCall: Tokenizable + AbiDecode + Send + Sync { /// The name of the function fn function_name() -> Cow<'static, str>; /// Retrieves the ABI signature for the call fn abi_signature() -> Cow<'static, str>; /// The selector of the function fn selector() -> Selector { id(Self::abi_signature()) } } impl AbiEncode for T { fn encode(self) -> Result { let tokens = self.into_tokens(); let selector = Self::selector(); let encoded = ethers_core::abi::encode(&tokens); let encoded: Vec<_> = selector .iter() .copied() .chain(encoded.into_iter()) .collect(); Ok(encoded.into()) } } #[derive(ThisError, Debug)] /// An Error which is thrown when interacting with a smart contract pub enum ContractError { /// Thrown when the ABI decoding fails #[error(transparent)] DecodingError(#[from] ethers_core::abi::Error), /// Thrown when the internal BaseContract errors #[error(transparent)] AbiError(#[from] AbiError), /// Thrown when detokenizing an argument #[error(transparent)] DetokenizationError(#[from] InvalidOutputType), /// Thrown when a middleware call fails #[error("{0}")] MiddlewareError(M::Error), /// Thrown when a provider call fails #[error("{0}")] ProviderError(ProviderError), /// Thrown during deployment if a constructor argument was passed in the `deploy` /// call but a constructor was not present in the ABI #[error("constructor is not defined in the ABI")] ConstructorError, /// Thrown if a contract address is not found in the deployment transaction's /// receipt #[error("Contract was not deployed")] ContractNotDeployed, } #[derive(Debug, Clone)] #[must_use = "contract calls do nothing unless you `send` or `call` them"] /// Helper for managing a transaction before submitting it to a node pub struct ContractCall { /// The raw transaction object pub tx: TypedTransaction, /// The ABI of the function being called pub function: Function, /// Optional block number to be used when calculating the transaction's gas and nonce pub block: Option, pub(crate) client: Arc, pub(crate) datatype: PhantomData, } impl ContractCall { /// Sets the `from` field in the transaction to the provided value pub fn from>(mut self, from: T) -> Self { self.tx.set_from(from.into()); self } /// Uses a Legacy transaction instead of an EIP-1559 one to execute the call 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 } /// Sets the `gas` field in the transaction to the provided value pub fn gas>(mut self, gas: T) -> Self { self.tx.set_gas(gas); self } /// Sets the `gas_price` field in the transaction to the provided value /// If the internal transaction is an EIP-1559 one, then it sets both /// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value pub fn gas_price>(mut self, gas_price: T) -> Self { self.tx.set_gas_price(gas_price); self } /// Sets the `value` field in the transaction to the provided value pub fn value>(mut self, value: T) -> Self { self.tx.set_value(value); self } /// Sets the `block` field for sending the tx to the chain pub fn block>(mut self, block: T) -> Self { self.block = Some(block.into()); self } } impl ContractCall where M: Middleware, D: Detokenize, { /// Returns the underlying transaction's ABI encoded data pub fn calldata(&self) -> Option { self.tx.data().cloned() } /// Returns the estimated gas cost for the underlying transaction to be executed pub async fn estimate_gas(&self) -> Result> { self.client .estimate_gas(&self.tx) .await .map_err(ContractError::MiddlewareError) } /// Queries the blockchain via an `eth_call` for the provided transaction. /// /// If executed on a non-state mutating smart contract function (i.e. `view`, `pure`) /// then it will return the raw data from the chain. /// /// If executed on a mutating smart contract function, it will do a "dry run" of the call /// and return the return type of the transaction without mutating the state /// /// Note: this function _does not_ send a transaction from your account pub async fn call(&self) -> Result> { let bytes = self .client .call(&self.tx.clone(), self.block) .await .map_err(ContractError::MiddlewareError)?; // decode output let data = decode_function_data(&self.function, &bytes, false)?; Ok(data) } /// Signs and broadcasts the provided transaction pub async fn send(&self) -> Result, ContractError> { self.client .send_transaction(self.tx.clone(), self.block) .await .map_err(ContractError::MiddlewareError) } }