diff --git a/Cargo.lock b/Cargo.lock index c9c7f975..752669a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,7 @@ dependencies = [ "rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/crates/ethers-contract/Cargo.toml b/crates/ethers-contract/Cargo.toml index c631f657..a15e95f7 100644 --- a/crates/ethers-contract/Cargo.toml +++ b/crates/ethers-contract/Cargo.toml @@ -16,6 +16,7 @@ serde = { version = "1.0.110", default-features = false } rustc-hex = { version = "2.1.0", default-features = false } thiserror = { version = "1.0.19", default-features = false } once_cell = "1.4.0" +tokio = { version = "0.2.21", default-features = false } [features] default = ["abigen"] diff --git a/crates/ethers-contract/src/call.rs b/crates/ethers-contract/src/call.rs index 87476dda..51f29fe9 100644 --- a/crates/ethers-contract/src/call.rs +++ b/crates/ethers-contract/src/call.rs @@ -55,6 +55,10 @@ where DetokenizationError(#[from] InvalidOutputType), #[error(transparent)] CallError(P::Error), + #[error("constructor is not defined in the ABI")] + ConstructorError, + #[error("Contract was not deployed")] + ContractNotDeployed, } impl<'a, P, N, S, D> ContractCall<'a, P, N, S, D> diff --git a/crates/ethers-contract/src/factory.rs b/crates/ethers-contract/src/factory.rs index cc49e8fb..d0be9264 100644 --- a/crates/ethers-contract/src/factory.rs +++ b/crates/ethers-contract/src/factory.rs @@ -1,12 +1,70 @@ -use crate::Contract; +use crate::{Contract, ContractError}; use ethers_providers::{networks::Network, JsonRpcClient}; use ethers_signers::{Client, Signer}; use ethers_types::{ abi::{Abi, Tokenize}, - Bytes, + Bytes, TransactionRequest, }; +use std::time::Duration; +use tokio::time; + +/// Poll for tx confirmation once every 7 seconds. +/// TODO: Can this be improved by replacing polling with an "on new block" subscription? +const POLL_INTERVAL: u64 = 7000; + +#[derive(Debug, Clone)] +pub struct Deployer<'a, P, N, S> { + client: &'a Client<'a, P, N, S>, + abi: &'a Abi, + tx: TransactionRequest, + confs: usize, + poll_interval: Duration, +} + +impl<'a, P, N, S> Deployer<'a, P, N, S> +where + S: Signer, + P: JsonRpcClient, + P::Error: 'static, + N: Network, +{ + pub fn poll_interval>(mut self, interval: T) -> Self { + self.poll_interval = interval.into(); + self + } + + pub fn confirmations>(mut self, confirmations: T) -> Self { + self.confs = confirmations.into(); + self + } + + pub async fn send(self) -> Result, ContractError

> { + let tx_hash = self + .client + .send_transaction(self.tx, None) + .await + .map_err(ContractError::CallError)?; + + // poll for the receipt + let address; + loop { + if let Ok(receipt) = self.client.get_transaction_receipt(tx_hash).await { + address = receipt + .contract_address + .ok_or(ContractError::ContractNotDeployed)?; + break; + } + + time::delay_for(Duration::from_millis(POLL_INTERVAL)).await; + } + + let contract = Contract::new(self.client, self.abi, address); + Ok(contract) + } +} + #[derive(Debug, Clone)] pub struct ContractFactory<'a, P, N, S> { client: &'a Client<'a, P, N, S>, @@ -14,7 +72,13 @@ pub struct ContractFactory<'a, P, N, S> { bytecode: &'a Bytes, } -impl<'a, P: JsonRpcClient, N: Network, S: Signer> ContractFactory<'a, P, N, S> { +impl<'a, P, N, S> ContractFactory<'a, P, N, S> +where + S: Signer, + P: JsonRpcClient, + P::Error: 'static, + N: Network, +{ /// Instantiate a new contract factory pub fn new(client: &'a Client<'a, P, N, S>, abi: &'a Abi, bytecode: &'a Bytes) -> Self { Self { @@ -27,17 +91,34 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> ContractFactory<'a, P, N, S> { /// Deploys an instance of the contract with the provider constructor arguments /// and returns the contract's instance pub async fn deploy( + &self, constructor_args: T, - ) -> Result, P::Error> { - // 1. Encode the constructor args - // - // 2. Create the runtime bytecode by concatenating the bytecode with the constructor - // arguments (?) - // - // 3. Call `client.send_transaction()` to deploy - // - // 4. Get the address of the contract from the transaction receipt - // - // 5. Instantiate & return the contract object + ) -> Result, ContractError

> { + // Encode the constructor args & concatenate with the bytecode if necessary + let params = constructor_args.into_tokens(); + let data: Bytes = match (self.abi.constructor(), params.is_empty()) { + (None, false) => { + return Err(ContractError::ConstructorError); + } + (None, true) => self.bytecode.clone(), + (Some(constructor), _) => { + Bytes(constructor.encode_input(self.bytecode.0.clone(), ¶ms)?) + } + }; + + // create the tx object. Since we're deploying a contract, `to` is `None` + let tx = TransactionRequest { + to: None, + data: Some(data), + ..Default::default() + }; + + Ok(Deployer { + client: self.client, + abi: self.abi, + tx, + confs: 1, + poll_interval: Duration::from_millis(POLL_INTERVAL), + }) } }