use crate::{ContractError, ContractInstance};
use ethers_core::{
abi::{Abi, Token, Tokenize},
types::{
transaction::eip2718::TypedTransaction, Address, BlockNumber, Bytes, NameOrAddress,
TransactionReceipt, TransactionRequest, U256, U64,
},
};
use ethers_providers::{
call_raw::{CallBuilder, RawCall},
Middleware,
};
#[cfg(not(feature = "legacy"))]
use ethers_core::types::Eip1559TransactionRequest;
use std::{borrow::Borrow, marker::PhantomData, sync::Arc};
pub type ContractDeployer<M, C> = ContractDeploymentTx<Arc<M>, M, C>;
pub type ContractFactory<M> = DeploymentTxFactory<Arc<M>, M>;
#[derive(Debug)]
#[must_use = "DeploymentTx does nothing unless you `send` it"]
pub struct ContractDeploymentTx<B, M, C> {
pub deployer: Deployer<B, M>,
_contract: PhantomData<C>,
}
impl<B, M, C> Clone for ContractDeploymentTx<B, M, C>
where
B: Clone,
{
fn clone(&self) -> Self {
ContractDeploymentTx { deployer: self.deployer.clone(), _contract: self._contract }
}
}
impl<B, M, C> From<Deployer<B, M>> for ContractDeploymentTx<B, M, C> {
fn from(deployer: Deployer<B, M>) -> Self {
Self { deployer, _contract: PhantomData }
}
}
impl<B, M, C> ContractDeploymentTx<B, M, C>
where
B: Borrow<M> + Clone,
M: Middleware,
C: From<ContractInstance<B, M>>,
{
pub fn new(deployer: Deployer<B, M>) -> Self {
Self { deployer, _contract: PhantomData }
}
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
self.deployer.confs = confirmations.into();
self
}
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.deployer.block = block.into();
self
}
pub fn legacy(mut self) -> Self {
self.deployer = self.deployer.legacy();
self
}
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
self.deployer.tx.set_from(from.into());
self
}
pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
self.deployer.tx.set_to(to.into());
self
}
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
self.deployer.tx.set_gas(gas.into());
self
}
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
self.deployer.tx.set_gas_price(gas_price.into());
self
}
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
self.deployer.tx.set_value(value.into());
self
}
pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
self.deployer.tx.set_data(data.into());
self
}
pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
self.deployer.tx.set_nonce(nonce.into());
self
}
pub fn chain_id<T: Into<U64>>(mut self, chain_id: T) -> Self {
self.deployer.tx.set_chain_id(chain_id.into());
self
}
pub async fn call(&self) -> Result<(), ContractError<M>> {
self.deployer.call().await
}
pub fn call_raw(&self) -> CallBuilder<'_, M::Provider> {
self.deployer.call_raw()
}
pub async fn send(self) -> Result<C, ContractError<M>> {
let contract = self.deployer.send().await?;
Ok(C::from(contract))
}
pub async fn send_with_receipt(self) -> Result<(C, TransactionReceipt), ContractError<M>> {
let (contract, receipt) = self.deployer.send_with_receipt().await?;
Ok((C::from(contract), receipt))
}
pub fn abi(&self) -> &Abi {
self.deployer.abi()
}
pub fn client(&self) -> &M {
self.deployer.client()
}
}
#[derive(Debug)]
#[must_use = "Deployer does nothing unless you `send` it"]
pub struct Deployer<B, M> {
pub tx: TypedTransaction,
abi: Abi,
client: B,
confs: usize,
block: BlockNumber,
_m: PhantomData<M>,
}
impl<B, M> Clone for Deployer<B, M>
where
B: Clone,
{
fn clone(&self) -> Self {
Deployer {
tx: self.tx.clone(),
abi: self.abi.clone(),
client: self.client.clone(),
confs: self.confs,
block: self.block,
_m: PhantomData,
}
}
}
impl<B, M> Deployer<B, M>
where
B: Borrow<M> + Clone,
M: Middleware,
{
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
self.confs = confirmations.into();
self
}
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.block = block.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 async fn call(&self) -> Result<(), ContractError<M>> {
self.client
.borrow()
.call(&self.tx, Some(self.block.into()))
.await
.map_err(ContractError::from_middleware_error)?;
Ok(())
}
pub fn call_raw(&self) -> CallBuilder<'_, M::Provider> {
self.client.borrow().provider().call_raw(&self.tx).block(self.block.into())
}
pub async fn send(self) -> Result<ContractInstance<B, M>, ContractError<M>> {
let (contract, _) = self.send_with_receipt().await?;
Ok(contract)
}
pub async fn send_with_receipt(
self,
) -> Result<(ContractInstance<B, M>, TransactionReceipt), ContractError<M>> {
let pending_tx = self
.client
.borrow()
.send_transaction(self.tx, Some(self.block.into()))
.await
.map_err(ContractError::from_middleware_error)?;
let receipt = pending_tx
.confirmations(self.confs)
.await
.ok()
.flatten()
.ok_or(ContractError::ContractNotDeployed)?;
let address = receipt.contract_address.ok_or(ContractError::ContractNotDeployed)?;
let contract = ContractInstance::new(address, self.abi, self.client);
Ok((contract, receipt))
}
pub fn abi(&self) -> &Abi {
&self.abi
}
pub fn client(&self) -> &M {
self.client.borrow()
}
}
#[derive(Debug)]
pub struct DeploymentTxFactory<B, M> {
client: B,
abi: Abi,
bytecode: Bytes,
_m: PhantomData<M>,
}
impl<B, M> Clone for DeploymentTxFactory<B, M>
where
B: Clone,
{
fn clone(&self) -> Self {
DeploymentTxFactory {
client: self.client.clone(),
abi: self.abi.clone(),
bytecode: self.bytecode.clone(),
_m: PhantomData,
}
}
}
impl<B, M> DeploymentTxFactory<B, M>
where
B: Borrow<M> + Clone,
M: Middleware,
{
pub fn new(abi: Abi, bytecode: Bytes, client: B) -> Self {
Self { client, abi, bytecode, _m: PhantomData }
}
pub fn deploy_tokens(self, params: Vec<Token>) -> Result<Deployer<B, M>, ContractError<M>>
where
B: Clone,
{
let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
(None, false) => return Err(ContractError::ConstructorError),
(None, true) => self.bytecode.clone(),
(Some(constructor), _) => {
constructor.encode_input(self.bytecode.to_vec(), ¶ms)?.into()
}
};
#[cfg(feature = "legacy")]
let tx = TransactionRequest { to: None, data: Some(data), ..Default::default() };
#[cfg(not(feature = "legacy"))]
let tx = Eip1559TransactionRequest { to: None, data: Some(data), ..Default::default() };
let tx = tx.into();
Ok(Deployer {
client: self.client.clone(),
abi: self.abi,
tx,
confs: 1,
block: BlockNumber::Latest,
_m: PhantomData,
})
}
pub fn deploy<T: Tokenize>(
self,
constructor_args: T,
) -> Result<Deployer<B, M>, ContractError<M>> {
self.deploy_tokens(constructor_args.into_tokens())
}
}