2021-12-19 04:28:38 +00:00
|
|
|
#![allow(clippy::return_self_not_must_use)]
|
|
|
|
|
2023-02-22 22:52:25 +00:00
|
|
|
use crate::EthError;
|
|
|
|
|
2020-12-31 19:08:12 +00:00
|
|
|
use super::base::{decode_function_data, AbiError};
|
2020-05-31 16:01:34 +00:00
|
|
|
use ethers_core::{
|
2021-10-27 22:07:24 +00:00
|
|
|
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
|
2021-08-09 00:50:38 +00:00
|
|
|
types::{
|
2021-10-27 22:07:24 +00:00
|
|
|
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Selector,
|
|
|
|
TransactionRequest, U256,
|
2021-08-09 00:50:38 +00:00
|
|
|
},
|
2021-10-27 22:07:24 +00:00
|
|
|
utils::id,
|
2020-05-28 09:04:12 +00:00
|
|
|
};
|
2022-06-04 21:18:25 +00:00
|
|
|
use ethers_providers::{
|
|
|
|
call_raw::{CallBuilder, RawCall},
|
2023-02-22 22:52:25 +00:00
|
|
|
JsonRpcError, Middleware, MiddlewareError, PendingTransaction, ProviderError,
|
2022-06-04 21:18:25 +00:00
|
|
|
};
|
2020-05-27 08:46:16 +00:00
|
|
|
|
2022-11-07 01:54:18 +00:00
|
|
|
use std::{
|
2023-02-06 21:27:01 +00:00
|
|
|
borrow::{Borrow, Cow},
|
2022-11-07 01:54:18 +00:00
|
|
|
fmt::Debug,
|
|
|
|
future::{Future, IntoFuture},
|
|
|
|
marker::PhantomData,
|
|
|
|
pin::Pin,
|
|
|
|
};
|
2020-05-27 08:46:16 +00:00
|
|
|
|
|
|
|
use thiserror::Error as ThisError;
|
|
|
|
|
2021-10-18 10:28:38 +00:00
|
|
|
/// A helper trait for types that represent all call input parameters of a specific function
|
2021-10-27 22:07:24 +00:00
|
|
|
pub trait EthCall: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
|
2021-10-18 10:28:38 +00:00
|
|
|
/// 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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 23:15:33 +00:00
|
|
|
#[derive(ThisError, Debug)]
|
2020-06-10 18:20:47 +00:00
|
|
|
/// An Error which is thrown when interacting with a smart contract
|
2020-09-24 21:33:09 +00:00
|
|
|
pub enum ContractError<M: Middleware> {
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown when the ABI decoding fails
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error(transparent)]
|
2020-10-29 07:48:24 +00:00
|
|
|
DecodingError(#[from] ethers_core::abi::Error),
|
|
|
|
|
|
|
|
/// Thrown when the internal BaseContract errors
|
|
|
|
#[error(transparent)]
|
|
|
|
AbiError(#[from] AbiError),
|
2020-06-01 23:15:33 +00:00
|
|
|
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown when detokenizing an argument
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error(transparent)]
|
|
|
|
DetokenizationError(#[from] InvalidOutputType),
|
|
|
|
|
2021-01-22 09:25:22 +00:00
|
|
|
/// Thrown when a middleware call fails
|
2023-02-22 22:52:25 +00:00
|
|
|
#[error("{e}")]
|
|
|
|
MiddlewareError {
|
|
|
|
/// The underlying error
|
|
|
|
e: M::Error,
|
|
|
|
},
|
2020-06-01 23:15:33 +00:00
|
|
|
|
2021-01-22 09:25:22 +00:00
|
|
|
/// Thrown when a provider call fails
|
2023-02-22 22:52:25 +00:00
|
|
|
#[error("{e}")]
|
|
|
|
ProviderError {
|
|
|
|
/// The underlying error
|
|
|
|
e: ProviderError,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// Contract reverted
|
|
|
|
#[error("Contract call reverted with data: {0}")]
|
|
|
|
Revert(Bytes),
|
2021-01-22 09:25:22 +00:00
|
|
|
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown during deployment if a constructor argument was passed in the `deploy`
|
|
|
|
/// call but a constructor was not present in the ABI
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error("constructor is not defined in the ABI")]
|
|
|
|
ConstructorError,
|
|
|
|
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown if a contract address is not found in the deployment transaction's
|
|
|
|
/// receipt
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error("Contract was not deployed")]
|
|
|
|
ContractNotDeployed,
|
|
|
|
}
|
|
|
|
|
2023-02-22 22:52:25 +00:00
|
|
|
impl<M: Middleware> ContractError<M> {
|
|
|
|
/// If this `ContractError` is a revert, this method will retrieve a
|
|
|
|
/// reference to the underlying revert data. This ABI-encoded data could be
|
|
|
|
/// a String, or a custom Solidity error type.
|
|
|
|
///
|
|
|
|
/// ## Returns
|
|
|
|
///
|
|
|
|
/// `None` if the error is not a revert
|
|
|
|
/// `Some(data)` with the revert data, if the error is a revert
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// To skip this step, consider using [`ContractError::decode_revert`]
|
|
|
|
pub fn as_revert(&self) -> Option<&Bytes> {
|
|
|
|
match self {
|
|
|
|
ContractError::Revert(data) => Some(data),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// True if the error is a revert, false otherwise
|
|
|
|
pub fn is_revert(&self) -> bool {
|
|
|
|
matches!(self, ContractError::Revert(_))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Decode revert data into an [`EthError`] type. Returns `None` if
|
|
|
|
/// decoding fails, or if this is not a revert
|
|
|
|
pub fn decode_revert<Err: EthError>(&self) -> Option<Err> {
|
|
|
|
self.as_revert().and_then(|data| Err::decode_with_selector(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a [`MiddlewareError`] to a `ContractError`
|
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a `ContractError` to a [`MiddlewareError`] if possible.
|
|
|
|
pub fn as_middleware_error(&self) -> Option<&M::Error> {
|
|
|
|
match self {
|
|
|
|
ContractError::MiddlewareError { e } => Some(e),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// True if the error is a middleware error
|
|
|
|
pub fn is_middleware_error(&self) -> bool {
|
|
|
|
matches!(self, ContractError::MiddlewareError { .. })
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a `ContractError` to a [`ProviderError`] if possible.
|
|
|
|
pub fn as_provider_error(&self) -> Option<&ProviderError> {
|
|
|
|
match self {
|
|
|
|
ContractError::ProviderError { e } => Some(e),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// True if the error is a provider error
|
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:27:01 +00:00
|
|
|
/// `ContractCall` is a [`FunctionCall`] object with an [`std::sync::Arc`] middleware.
|
|
|
|
/// This type alias exists to preserve backwards compatibility with
|
|
|
|
/// less-abstract Contracts.
|
|
|
|
///
|
|
|
|
/// For full usage docs, see [`FunctionCall`].
|
|
|
|
pub type ContractCall<M, D> = FunctionCall<std::sync::Arc<M>, M, D>;
|
|
|
|
|
2022-04-27 12:33:22 +00:00
|
|
|
#[derive(Debug)]
|
2020-06-17 06:38:04 +00:00
|
|
|
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Helper for managing a transaction before submitting it to a node
|
2023-02-06 21:27:01 +00:00
|
|
|
pub struct FunctionCall<B, M, D> {
|
2020-06-10 18:20:47 +00:00
|
|
|
/// The raw transaction object
|
2021-08-09 00:50:38 +00:00
|
|
|
pub tx: TypedTransaction,
|
2020-06-10 18:20:47 +00:00
|
|
|
/// The ABI of the function being called
|
|
|
|
pub function: Function,
|
|
|
|
/// Optional block number to be used when calculating the transaction's gas and nonce
|
2021-03-16 19:46:07 +00:00
|
|
|
pub block: Option<BlockId>,
|
2023-02-06 21:27:01 +00:00
|
|
|
pub(crate) client: B,
|
2020-05-27 08:46:16 +00:00
|
|
|
pub(crate) datatype: PhantomData<D>,
|
2023-02-06 21:27:01 +00:00
|
|
|
pub(crate) _m: PhantomData<M>,
|
2020-05-27 08:46:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:27:01 +00:00
|
|
|
impl<B, M, D> Clone for FunctionCall<B, M, D>
|
|
|
|
where
|
|
|
|
B: Clone,
|
|
|
|
{
|
2022-04-27 12:33:22 +00:00
|
|
|
fn clone(&self) -> Self {
|
2023-02-06 21:27:01 +00:00
|
|
|
FunctionCall {
|
2022-04-27 12:33:22 +00:00
|
|
|
tx: self.tx.clone(),
|
|
|
|
function: self.function.clone(),
|
|
|
|
block: self.block,
|
|
|
|
client: self.client.clone(),
|
|
|
|
datatype: self.datatype,
|
2023-02-06 21:27:01 +00:00
|
|
|
_m: self._m,
|
2022-04-27 12:33:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:27:01 +00:00
|
|
|
impl<B, M, D> FunctionCall<B, M, D>
|
|
|
|
where
|
|
|
|
B: Borrow<M>,
|
|
|
|
D: Detokenize,
|
|
|
|
{
|
2020-05-27 08:46:16 +00:00
|
|
|
/// Sets the `from` field in the transaction to the provided value
|
|
|
|
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
2021-08-09 00:50:38 +00:00
|
|
|
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,
|
|
|
|
};
|
2020-05-27 08:46:16 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `gas` field in the transaction to the provided value
|
|
|
|
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
2021-08-09 00:50:38 +00:00
|
|
|
self.tx.set_gas(gas);
|
2020-05-27 08:46:16 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `gas_price` field in the transaction to the provided value
|
2021-08-09 00:50:38 +00:00
|
|
|
/// 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
|
2020-05-27 08:46:16 +00:00
|
|
|
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
2021-08-09 00:50:38 +00:00
|
|
|
self.tx.set_gas_price(gas_price);
|
2020-05-27 08:46:16 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `value` field in the transaction to the provided value
|
|
|
|
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
2021-08-09 00:50:38 +00:00
|
|
|
self.tx.set_value(value);
|
2020-05-27 08:46:16 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-06-01 23:15:33 +00:00
|
|
|
/// Sets the `block` field for sending the tx to the chain
|
2021-03-16 19:46:07 +00:00
|
|
|
pub fn block<T: Into<BlockId>>(mut self, block: T) -> Self {
|
2020-06-01 23:15:33 +00:00
|
|
|
self.block = Some(block.into());
|
|
|
|
self
|
|
|
|
}
|
2020-05-27 08:46:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:27:01 +00:00
|
|
|
impl<B, M, D> FunctionCall<B, M, D>
|
2020-05-27 08:46:16 +00:00
|
|
|
where
|
2023-02-06 21:27:01 +00:00
|
|
|
B: Borrow<M>,
|
2020-09-24 21:33:09 +00:00
|
|
|
M: Middleware,
|
2020-05-27 15:43:43 +00:00
|
|
|
D: Detokenize,
|
2020-05-27 08:46:16 +00:00
|
|
|
{
|
2020-08-12 08:35:33 +00:00
|
|
|
/// Returns the underlying transaction's ABI encoded data
|
|
|
|
pub fn calldata(&self) -> Option<Bytes> {
|
2021-08-09 00:50:38 +00:00
|
|
|
self.tx.data().cloned()
|
2020-08-12 08:35:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the estimated gas cost for the underlying transaction to be executed
|
2020-09-24 21:33:09 +00:00
|
|
|
pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
|
2023-02-06 21:27:01 +00:00
|
|
|
self.client
|
|
|
|
.borrow()
|
|
|
|
.estimate_gas(&self.tx, self.block)
|
|
|
|
.await
|
2023-02-22 22:52:25 +00:00
|
|
|
.map_err(ContractError::from_middleware_error)
|
2020-08-12 08:35:33 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 08:46:16 +00:00
|
|
|
/// 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
|
2020-09-24 21:33:09 +00:00
|
|
|
pub async fn call(&self) -> Result<D, ContractError<M>> {
|
2023-02-22 22:52:25 +00:00
|
|
|
let bytes = self
|
|
|
|
.client
|
|
|
|
.borrow()
|
|
|
|
.call(&self.tx, self.block)
|
|
|
|
.await
|
|
|
|
.map_err(ContractError::from_middleware_error)?;
|
2020-05-27 08:46:16 +00:00
|
|
|
|
2020-10-29 07:48:24 +00:00
|
|
|
// decode output
|
2020-12-31 19:08:12 +00:00
|
|
|
let data = decode_function_data(&self.function, &bytes, false)?;
|
2020-05-27 08:46:16 +00:00
|
|
|
|
|
|
|
Ok(data)
|
|
|
|
}
|
|
|
|
|
2022-06-04 21:18:25 +00:00
|
|
|
/// Returns an implementer of [`RawCall`] which can be `.await`d to query the blockchain via
|
|
|
|
/// `eth_call`, returning the deoded return data.
|
|
|
|
///
|
|
|
|
/// The returned call can also be used to override the input parameters to `eth_call`.
|
|
|
|
///
|
|
|
|
/// Note: this function _does not_ send a transaction from your account
|
|
|
|
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>| {
|
2023-02-22 22:52:25 +00:00
|
|
|
let bytes = res?;
|
2022-06-04 21:18:25 +00:00
|
|
|
decode_function_data(&self.function, &bytes, false).map_err(From::from)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a [`CallBuilder`] which can be `.await`d to query the blochcain via `eth_call`,
|
|
|
|
/// returning the raw bytes from the transaction.
|
|
|
|
///
|
|
|
|
/// The returned call can also be used to override the input parameters to `eth_call`.
|
|
|
|
///
|
|
|
|
/// Note: this function _does not_ send a transaction from your account
|
|
|
|
pub fn call_raw_bytes(&self) -> CallBuilder<'_, M::Provider> {
|
2023-02-06 21:27:01 +00:00
|
|
|
let call = self.client.borrow().provider().call_raw(&self.tx);
|
2022-06-04 21:18:25 +00:00
|
|
|
if let Some(block) = self.block {
|
|
|
|
call.block(block)
|
|
|
|
} else {
|
|
|
|
call
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 08:46:16 +00:00
|
|
|
/// Signs and broadcasts the provided transaction
|
2020-12-17 11:26:01 +00:00
|
|
|
pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> {
|
2020-09-24 21:33:09 +00:00
|
|
|
self.client
|
2023-02-06 21:27:01 +00:00
|
|
|
.borrow()
|
2020-12-17 11:26:01 +00:00
|
|
|
.send_transaction(self.tx.clone(), self.block)
|
2020-09-24 21:33:09 +00:00
|
|
|
.await
|
2023-02-22 22:52:25 +00:00
|
|
|
.map_err(ContractError::from_middleware_error)
|
2020-05-27 08:46:16 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-07 01:54:18 +00:00
|
|
|
|
2023-02-06 21:27:01 +00:00
|
|
|
/// [`FunctionCall`] can be turned into [`Future`] automatically with `.await`.
|
|
|
|
/// Defaults to calling [`FunctionCall::call`].
|
|
|
|
impl<B, M, D> IntoFuture for FunctionCall<B, M, D>
|
2022-11-07 01:54:18 +00:00
|
|
|
where
|
|
|
|
Self: 'static,
|
2023-02-06 21:27:01 +00:00
|
|
|
B: Borrow<M> + Send + Sync,
|
2022-11-07 01:54:18 +00:00
|
|
|
M: Middleware,
|
2023-01-27 19:54:49 +00:00
|
|
|
D: Detokenize + Send + Sync,
|
2022-11-07 01:54:18 +00:00
|
|
|
{
|
|
|
|
type Output = Result<D, ContractError<M>>;
|
2023-01-30 20:06:34 +00:00
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2023-01-27 19:54:49 +00:00
|
|
|
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
2022-11-07 01:54:18 +00:00
|
|
|
|
|
|
|
fn into_future(self) -> Self::IntoFuture {
|
|
|
|
Box::pin(async move { self.call().await })
|
|
|
|
}
|
|
|
|
}
|