2020-05-31 16:01:34 +00:00
|
|
|
use ethers_core::{
|
2020-05-28 09:04:12 +00:00
|
|
|
abi::{Detokenize, Error as AbiError, Function, InvalidOutputType},
|
2020-06-22 08:44:08 +00:00
|
|
|
types::{Address, BlockNumber, TransactionRequest, TxHash, U256},
|
2020-05-28 09:04:12 +00:00
|
|
|
};
|
2020-06-22 08:44:08 +00:00
|
|
|
use ethers_providers::{JsonRpcClient, ProviderError};
|
2020-06-01 23:15:33 +00:00
|
|
|
use ethers_signers::{Client, ClientError, Signer};
|
2020-05-27 08:46:16 +00:00
|
|
|
|
2020-06-22 08:44:08 +00:00
|
|
|
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
2020-05-27 08:46:16 +00:00
|
|
|
|
|
|
|
use thiserror::Error as ThisError;
|
|
|
|
|
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-06-01 23:15:33 +00:00
|
|
|
pub enum ContractError {
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown when the ABI decoding fails
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error(transparent)]
|
|
|
|
DecodingError(#[from] AbiError),
|
|
|
|
|
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),
|
|
|
|
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown when a client call fails
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error(transparent)]
|
|
|
|
ClientError(#[from] ClientError),
|
|
|
|
|
2020-06-10 18:20:47 +00:00
|
|
|
/// Thrown when a provider call fails
|
2020-06-01 23:15:33 +00:00
|
|
|
#[error(transparent)]
|
|
|
|
ProviderError(#[from] ProviderError),
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2020-06-02 10:36:02 +00:00
|
|
|
#[derive(Debug, Clone)]
|
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
|
2020-06-22 08:44:08 +00:00
|
|
|
pub struct ContractCall<P, S, D> {
|
2020-06-10 18:20:47 +00:00
|
|
|
/// The raw transaction object
|
|
|
|
pub tx: TransactionRequest,
|
|
|
|
/// 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<BlockNumber>,
|
2020-06-22 08:44:08 +00:00
|
|
|
pub(crate) client: Arc<Client<P, S>>,
|
2020-05-27 08:46:16 +00:00
|
|
|
pub(crate) datatype: PhantomData<D>,
|
|
|
|
}
|
|
|
|
|
2020-06-22 08:44:08 +00:00
|
|
|
impl<P, S, D: Detokenize> ContractCall<P, S, D> {
|
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 {
|
|
|
|
self.tx.from = Some(from.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `gas` field in the transaction to the provided value
|
|
|
|
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
|
|
|
self.tx.gas = Some(gas.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `gas_price` field in the transaction to the provided value
|
|
|
|
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
|
|
|
self.tx.gas_price = Some(gas_price.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `value` field in the transaction to the provided value
|
|
|
|
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
|
|
|
self.tx.value = Some(value.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-06-01 23:15:33 +00:00
|
|
|
/// Sets the `block` field for sending the tx to the chain
|
|
|
|
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
|
|
|
self.block = Some(block.into());
|
|
|
|
self
|
|
|
|
}
|
2020-05-27 08:46:16 +00:00
|
|
|
}
|
|
|
|
|
2020-06-22 08:44:08 +00:00
|
|
|
impl<P, S, D> ContractCall<P, S, D>
|
2020-05-27 08:46:16 +00:00
|
|
|
where
|
2020-05-27 15:43:43 +00:00
|
|
|
S: Signer,
|
|
|
|
P: JsonRpcClient,
|
|
|
|
D: Detokenize,
|
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-06-02 10:36:02 +00:00
|
|
|
pub async fn call(&self) -> Result<D, ContractError> {
|
|
|
|
let bytes = self.client.call(&self.tx, self.block).await?;
|
2020-05-27 08:46:16 +00:00
|
|
|
|
|
|
|
let tokens = self.function.decode_output(&bytes.0)?;
|
|
|
|
let data = D::from_tokens(tokens)?;
|
|
|
|
|
|
|
|
Ok(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Signs and broadcasts the provided transaction
|
2020-06-22 08:44:08 +00:00
|
|
|
pub async fn send(self) -> Result<TxHash, ContractError> {
|
2020-06-01 23:15:33 +00:00
|
|
|
Ok(self.client.send_transaction(self.tx, self.block).await?)
|
2020-05-27 08:46:16 +00:00
|
|
|
}
|
|
|
|
}
|