diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index 5ad5f828..4f89938c 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -44,7 +44,7 @@ impl Context {
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
// 0. Imports
- let imports = common::imports();
+ let imports = common::imports(&name.to_string());
// 1. Declare Contract struct
let struct_decl = common::struct_declaration(&cx, &abi_name);
@@ -67,12 +67,12 @@ impl Context {
#struct_decl
- impl<'a, P: JsonRpcClient, S: Signer> #name<'a, P, S> {
+ impl<'a, P: JsonRpcClient, S: Signer> #name
{
/// Creates a new contract instance with the specified `ethers`
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object
- pub fn new>(address: T, client: &'a Client) -> Self {
- let contract = Contract::new(address.into(), #abi_name.clone(), client);
+ pub fn new, C: Into>>>(address: T, client: C) -> Self {
+ let contract = Contract::new(address.into(), #abi_name.clone(), client.into());
Self(contract)
}
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index 4b2f1677..2e969254 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -1,15 +1,18 @@
-use super::Context;
+use super::{util, Context};
use ethers_core::types::Address;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
-pub(crate) fn imports() -> TokenStream {
+pub(crate) fn imports(name: &str) -> TokenStream {
+ let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
+
quote! {
#![allow(dead_code)]
#![allow(unused_imports)]
- // TODO: Can we make this context aware so that it imports either ethers_contract
- // or ethers::contract?
+ #doc
+
+ use std::sync::Arc;
use ethers::{
core::{
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
@@ -33,17 +36,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
// Struct declaration
#[derive(Clone)]
- pub struct #name<'a, P, S>(Contract<'a, P, S>);
+ pub struct #name(Contract
);
// Deref to the inner contract in order to access more specific functions functions
- impl<'a, P, S> std::ops::Deref for #name<'a, P, S> {
- type Target = Contract<'a, P, S>;
+ impl
std::ops::Deref for #name
{
+ type Target = Contract
;
fn deref(&self) -> &Self::Target { &self.0 }
}
- impl<'a, P: JsonRpcClient, S: Signer> std::fmt::Debug for #name<'a, P, S> {
+ impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple(stringify!(#name))
.field(&self.address())
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 4494b747..bf9c4df0 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -45,9 +45,9 @@ fn expand_function(function: &Function, alias: Option) -> Result }
+ quote! { ContractCall }
} else {
- quote! { ContractCall<'a, P, S, H256> }
+ quote! { ContractCall
}
};
let arg = expand_inputs_call_arg(&function.inputs);
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 17cde911..e8dc4234 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -1,11 +1,11 @@
use ethers_core::{
abi::{Detokenize, Error as AbiError, Function, InvalidOutputType},
- types::{Address, BlockNumber, TransactionRequest, U256},
+ types::{Address, BlockNumber, TransactionRequest, TxHash, U256},
};
-use ethers_providers::{JsonRpcClient, PendingTransaction, ProviderError};
+use ethers_providers::{JsonRpcClient, ProviderError};
use ethers_signers::{Client, ClientError, Signer};
-use std::{fmt::Debug, marker::PhantomData};
+use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use thiserror::Error as ThisError;
@@ -42,18 +42,18 @@ pub enum ContractError {
#[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<'a, P, S, D> {
+pub struct ContractCall
{
/// 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,
- pub(crate) client: &'a Client,
+ pub(crate) client: Arc>,
pub(crate) datatype: PhantomData,
}
-impl<'a, P, S, D: Detokenize> ContractCall<'a, P, S, D> {
+impl ContractCall
{
/// Sets the `from` field in the transaction to the provided value
pub fn from>(mut self, from: T) -> Self {
self.tx.from = Some(from.into());
@@ -85,7 +85,7 @@ impl<'a, P, S, D: Detokenize> ContractCall<'a, P, S, D> {
}
}
-impl<'a, P, S, D> ContractCall<'a, P, S, D>
+impl ContractCall
where
S: Signer,
P: JsonRpcClient,
@@ -111,7 +111,7 @@ where
}
/// Signs and broadcasts the provided transaction
- pub async fn send(self) -> Result, ContractError> {
+ pub async fn send(self) -> Result {
Ok(self.client.send_transaction(self.tx, self.block).await?)
}
}
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 002ed8b3..cf7bb3cc 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -2,13 +2,13 @@ use super::{call::ContractCall, event::Event};
use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
- types::{Address, Filter, NameOrAddress, Selector, TransactionRequest},
+ types::{Address, Filter, NameOrAddress, Selector, TransactionRequest, TxHash},
};
-use ethers_providers::JsonRpcClient;
+use ethers_providers::{JsonRpcClient, PendingTransaction};
use ethers_signers::{Client, Signer};
use rustc_hex::ToHex;
-use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
+use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
/// A Contract is an abstraction of an executable program on the Ethereum Blockchain.
/// It has code (called byte code) as well as allocated long-term memory
@@ -79,7 +79,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
/// .parse::()?.connect(provider);
///
/// // create the contract object at the address
-/// let contract = Contract::new(address, abi, &client);
+/// let contract = Contract::new(address, abi, client);
///
/// // Calling constant methods is done by calling `call()` on the method builder.
/// // (if the function takes no arguments, then you must use `()` as the argument)
@@ -90,9 +90,10 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
///
/// // Non-constant methods are executed via the `send()` call on the method builder.
/// let tx_hash = contract
-/// .method::<_, H256>("setValue", "hi".to_owned())?
-/// .send()
-/// .await?;
+/// .method::<_, H256>("setValue", "hi".to_owned())?.send().await?;
+///
+/// // `await`ing on the pending transaction resolves to a transaction receipt
+/// let receipt = contract.pending_transaction(tx_hash).confirmations(6).await?;
///
/// # Ok(())
/// # }
@@ -116,7 +117,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
/// # let provider = Provider::::try_from("http://localhost:8545").unwrap();
/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::()?.connect(provider);
-/// # let contract = Contract::new(address, abi, &client);
+/// # let contract = Contract::new(address, abi, client);
///
/// #[derive(Clone, Debug)]
/// struct ValueChanged {
@@ -162,8 +163,8 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
/// [`event`]: method@crate::Contract::event
/// [`method`]: method@crate::Contract::method
#[derive(Debug, Clone)]
-pub struct Contract<'a, P, S> {
- client: &'a Client,
+pub struct Contract
{
+ client: Arc>,
abi: Abi,
address: Address,
@@ -174,17 +175,17 @@ pub struct Contract<'a, P, S> {
methods: HashMap,
}
-impl<'a, P, S> Contract<'a, P, S>
+impl Contract
where
S: Signer,
P: JsonRpcClient,
{
/// Creates a new contract from the provided client, abi and address
- pub fn new(address: Address, abi: Abi, client: &'a Client
) -> Self {
+ pub fn new(address: Address, abi: Abi, client: impl Into>>) -> Self {
let methods = create_mapping(&abi.functions, |function| function.selector());
Self {
- client,
+ client: client.into(),
abi,
address,
methods,
@@ -212,7 +213,7 @@ where
&self,
name: &str,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// get the function
let function = self.abi.function(name)?;
self.method_func(function, args)
@@ -224,7 +225,7 @@ where
&self,
signature: Selector,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
let function = self
.methods
.get(&signature)
@@ -237,7 +238,7 @@ where
&self,
function: &Function,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// create the calldata
let data = function.encode_input(&args.into_tokens())?;
@@ -250,7 +251,7 @@ where
Ok(ContractCall {
tx,
- client: self.client,
+ client: Arc::clone(&self.client), // cheap clone behind the Arc
block: None,
function: function.to_owned(),
datatype: PhantomData,
@@ -272,7 +273,7 @@ where
/// Returns a new contract instance using the provided client
///
/// Clones `self` internally
- pub fn connect(&self, client: &'a Client) -> Self
+ pub fn connect(&self, client: Arc>) -> Self
where
P: Clone,
{
@@ -295,6 +296,12 @@ where
pub fn client(&self) -> &Client {
&self.client
}
+
+ /// Helper which creates a pending transaction object from a transaction hash
+ /// using the provider's polling interval
+ pub fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, P> {
+ self.client.provider().pending_transaction(tx_hash)
+ }
}
/// Utility function for creating a mapping between a unique signature and a
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index 1a0fee65..c8d8f4b2 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -7,18 +7,21 @@ use ethers_core::{
use ethers_providers::JsonRpcClient;
use ethers_signers::{Client, Signer};
+use std::{sync::Arc, time::Duration};
+
#[derive(Debug, Clone)]
/// Helper which manages the deployment transaction of a smart contract
-pub struct Deployer<'a, P, S> {
+pub struct Deployer
{
/// The deployer's transaction, exposed for overriding the defaults
pub tx: TransactionRequest,
abi: Abi,
- client: &'a Client
,
+ client: Arc>,
confs: usize,
block: BlockNumber,
+ interval: Duration,
}
-impl<'a, P, S> Deployer<'a, P, S>
+impl Deployer
where
S: Signer,
P: JsonRpcClient,
@@ -29,6 +32,12 @@ where
self
}
+ /// Sets the poll interval for the pending deployment transaction's inclusion
+ pub fn interval>(mut self, interval: T) -> Self {
+ self.interval = interval.into();
+ self
+ }
+
pub fn block>(mut self, block: T) -> Self {
self.block = block.into();
self
@@ -37,13 +46,17 @@ where
/// Broadcasts the contract deployment transaction and after waiting for it to
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
/// struct at the deployed contract's address.
- pub async fn send(self) -> Result, ContractError> {
- let pending_tx = self
+ pub async fn send(self) -> Result, ContractError> {
+ let tx_hash = self
.client
.send_transaction(self.tx, Some(self.block))
.await?;
-
- let receipt = pending_tx.confirmations(self.confs).await?;
+ let receipt = self
+ .client
+ .pending_transaction(tx_hash)
+ .interval(self.interval)
+ .confirmations(self.confs)
+ .await?;
let address = receipt
.contract_address
@@ -97,7 +110,7 @@ where
/// .parse::()?.connect(provider);
///
/// // create a factory which will be used to deploy instances of the contract
-/// let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), &client);
+/// let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client);
///
/// // The deployer created by the `deploy` call exposes a builder which gets consumed
/// // by the async `send` call
@@ -109,13 +122,13 @@ where
/// println!("{}", contract.address());
/// # Ok(())
/// # }
-pub struct ContractFactory<'a, P, S> {
- client: &'a Client,
+pub struct ContractFactory
{
+ client: Arc>,
abi: Abi,
bytecode: Bytes,
}
-impl<'a, P, S> ContractFactory<'a, P, S>
+impl ContractFactory
where
S: Signer,
P: JsonRpcClient,
@@ -123,9 +136,9 @@ where
/// Creates a factory for deployment of the Contract with bytecode, and the
/// constructor defined in the abi. The client will be used to send any deployment
/// transaction.
- pub fn new(abi: Abi, bytecode: Bytes, client: &'a Client
) -> Self {
+ pub fn new(abi: Abi, bytecode: Bytes, client: impl Into>>) -> Self {
Self {
- client,
+ client: client.into(),
abi,
bytecode,
}
@@ -139,10 +152,7 @@ where
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
/// 1. The default poll duration is 7 seconds.
/// 1. The default number of confirmations is 1 block.
- pub fn deploy(
- self,
- constructor_args: T,
- ) -> Result, ContractError> {
+ pub fn deploy(self, constructor_args: T) -> 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()) {
@@ -164,11 +174,12 @@ where
};
Ok(Deployer {
- client: self.client,
+ client: Arc::clone(&self.client), // cheap clone behind the arc
abi: self.abi,
tx,
confs: 1,
block: BlockNumber::Latest,
+ interval: self.client.get_interval(),
})
}
}
diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs
index 3e9c7890..0cb16c66 100644
--- a/ethers-contract/tests/common/mod.rs
+++ b/ethers-contract/tests/common/mod.rs
@@ -7,7 +7,7 @@ use ethers_contract::{Contract, ContractFactory};
use ethers_core::utils::{Ganache, GanacheInstance, Solc};
use ethers_providers::{Http, Provider};
use ethers_signers::{Client, Wallet};
-use std::convert::TryFrom;
+use std::{convert::TryFrom, sync::Arc, time::Duration};
// Note: We also provide the `abigen` macro for generating these bindings automatically
#[derive(Clone, Debug)]
@@ -44,17 +44,19 @@ pub fn compile() -> (Abi, Bytes) {
}
/// connects the private key to http://localhost:8545
-pub fn connect(private_key: &str) -> Client {
- let provider = Provider::::try_from("http://localhost:8545").unwrap();
- private_key.parse::().unwrap().connect(provider)
+pub fn connect(private_key: &str) -> Arc> {
+ let provider = Provider::::try_from("http://localhost:8545")
+ .unwrap()
+ .interval(Duration::from_millis(10u64));
+ Arc::new(private_key.parse::().unwrap().connect(provider))
}
/// Launches a ganache instance and deploys the SimpleStorage contract
-pub async fn deploy<'a>(
- client: &'a Client,
+pub async fn deploy(
+ client: Arc>,
abi: Abi,
bytecode: Bytes,
-) -> (GanacheInstance, Contract<'a, Http, Wallet>) {
+) -> (GanacheInstance, Contract) {
let ganache = Ganache::new()
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
.spawn();
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index af1ed40b..bea466a6 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -13,7 +13,7 @@ mod eth_tests {
utils::Ganache,
};
use serial_test::serial;
- use std::convert::TryFrom;
+ use std::{convert::TryFrom, sync::Arc, time::Duration};
#[tokio::test]
#[serial]
@@ -31,7 +31,7 @@ mod eth_tests {
let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e");
// create a factory which will be used to deploy instances of the contract
- let factory = ContractFactory::new(abi, bytecode, &client);
+ let factory = ContractFactory::new(abi, bytecode, client.clone());
// `send` consumes the deployer so it must be cloned for later re-use
// (practically it's not expected that you'll need to deploy multiple instances of
@@ -46,9 +46,11 @@ mod eth_tests {
let value = get_value.clone().call().await.unwrap();
assert_eq!(value, "initial value");
- // make a call with `client2`
+ // need to declare the method first, and only then send it
+ // this is because it internally clones an Arc which would otherwise
+ // get immediately dropped
let _tx_hash = contract
- .connect(&client2)
+ .connect(client2.clone())
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
.send()
@@ -59,7 +61,7 @@ mod eth_tests {
// we can also call contract methods at other addresses with the `at` call
// (useful when interacting with multiple ERC20s for example)
- let contract2_addr = deployer.clone().send().await.unwrap().address();
+ let contract2_addr = deployer.send().await.unwrap().address();
let contract2 = contract.at(contract2_addr);
let init_value: String = contract2
.method::<_, String>("getValue", ())
@@ -82,7 +84,7 @@ mod eth_tests {
async fn get_past_events() {
let (abi, bytecode) = compile();
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
- let (_ganache, contract) = deploy(&client, abi, bytecode).await;
+ let (_ganache, contract) = deploy(client.clone(), abi, bytecode).await;
// make a call with `client2`
let _tx_hash = contract
@@ -111,7 +113,7 @@ mod eth_tests {
async fn watch_events() {
let (abi, bytecode) = compile();
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
- let (_ganache, contract) = deploy(&client, abi, bytecode).await;
+ let (_ganache, contract) = deploy(client, abi, bytecode).await;
// We spawn the event listener:
let mut stream = contract
@@ -125,12 +127,13 @@ mod eth_tests {
// and we make a few calls
for i in 0..num_calls {
- let _tx_hash = contract
+ let tx_hash = contract
.method::<_, H256>("setValue", i.to_string())
.unwrap()
.send()
.await
.unwrap();
+ let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
}
for i in 0..num_calls {
@@ -144,22 +147,23 @@ mod eth_tests {
#[serial]
async fn signer_on_node() {
let (abi, bytecode) = compile();
- let provider = Provider::::try_from("http://localhost:8545").unwrap();
+ let provider = Provider::::try_from("http://localhost:8545")
+ .unwrap()
+ .interval(Duration::from_millis(10u64));
let deployer = "3cDB3d9e1B74692Bb1E3bb5fc81938151cA64b02"
.parse::()
.unwrap();
- let client = Client::from(provider).with_sender(deployer);
- let (_ganache, contract) = deploy(&client, abi, bytecode).await;
+ let client = Arc::new(Client::from(provider).with_sender(deployer));
+ let (_ganache, contract) = deploy(client, abi, bytecode).await;
// make a call without the signer
- let _tx = contract
+ let tx_hash = contract
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
.send()
.await
- .unwrap()
- .await
.unwrap();
+ let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
let value: String = contract
.method::<_, String>("getValue", ())
.unwrap()
@@ -178,7 +182,7 @@ mod celo_tests {
signers::Wallet,
types::BlockNumber,
};
- use std::convert::TryFrom;
+ use std::{convert::TryFrom, sync::Arc, time::Duration};
#[tokio::test]
async fn deploy_and_call_contract() {
@@ -192,9 +196,11 @@ mod celo_tests {
let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::()
.unwrap()
- .connect(provider);
+ .connect(provider)
+ .interval(Duration::from_millis(6000));
+ let client = Arc::new(client);
- let factory = ContractFactory::new(abi, bytecode, &client);
+ let factory = ContractFactory::new(abi, bytecode, client);
let deployer = factory.deploy("initial value".to_string()).unwrap();
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
@@ -207,13 +213,13 @@ mod celo_tests {
assert_eq!(value, "initial value");
// make a state mutating transaction
- let pending_tx = contract
+ let tx_hash = contract
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
.send()
.await
.unwrap();
- let _receipt = pending_tx.await.unwrap();
+ let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
let value: String = contract
.method("getValue", ())
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index e95036c3..e625b493 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -111,7 +111,7 @@ mod pending_transaction;
pub use pending_transaction::PendingTransaction;
mod stream;
-pub use stream::FilterStream;
+pub use stream::{FilterStream, DEFAULT_POLL_INTERVAL};
// re-export `StreamExt` so that consumers can call `next()` on the `FilterStream`
// without having to import futures themselves
pub use futures_util::StreamExt;
diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/pending_transaction.rs
index 2867bcfa..a8b72697 100644
--- a/ethers-providers/src/pending_transaction.rs
+++ b/ethers-providers/src/pending_transaction.rs
@@ -1,5 +1,5 @@
use crate::{
- stream::{interval, DEFAULT_POLL_DURATION},
+ stream::{interval, DEFAULT_POLL_INTERVAL},
JsonRpcClient, Provider, ProviderError,
};
use ethers_core::types::{TransactionReceipt, TxHash, U64};
@@ -38,7 +38,7 @@ impl<'a, P: JsonRpcClient> PendingTransaction<'a, P> {
confirmations: 1,
provider,
state: PendingTxState::GettingReceipt(fut),
- interval: Box::new(interval(DEFAULT_POLL_DURATION)),
+ interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
}
}
@@ -50,8 +50,8 @@ impl<'a, P: JsonRpcClient> PendingTransaction<'a, P> {
}
/// Sets the polling interval
- pub fn interval>(mut self, duration: T) -> Self {
- self.interval = Box::new(interval(Duration::from_millis(duration.into())));
+ pub fn interval>(mut self, duration: T) -> Self {
+ self.interval = Box::new(interval(duration.into()));
self
}
}
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index bafed72c..cbe020db 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -1,6 +1,6 @@
use crate::{
ens,
- stream::{FilterStream, FilterWatcher},
+ stream::{FilterStream, FilterWatcher, DEFAULT_POLL_INTERVAL},
Http as HttpProvider, JsonRpcClient, PendingTransaction,
};
@@ -17,7 +17,7 @@ use serde::Deserialize;
use thiserror::Error;
use url::{ParseError, Url};
-use std::{convert::TryFrom, fmt::Debug};
+use std::{convert::TryFrom, fmt::Debug, time::Duration};
/// An abstract provider for interacting with the [Ethereum JSON RPC
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated
@@ -41,7 +41,7 @@ use std::{convert::TryFrom, fmt::Debug};
/// # }
/// ```
#[derive(Clone, Debug)]
-pub struct Provider(P, Option
);
+pub struct Provider(P, Option
, Option);
#[derive(Debug, Error)]
/// An error thrown when making a call to the provider
@@ -72,7 +72,7 @@ pub enum FilterKind<'a> {
impl Provider {
/// Instantiate a new provider with a backend.
pub fn new(provider: P) -> Self {
- Self(provider, None)
+ Self(provider, None, None)
}
////// Blockchain Status
@@ -265,7 +265,7 @@ impl Provider {
pub async fn send_transaction(
&self,
mut tx: TransactionRequest,
- ) -> Result, ProviderError> {
+ ) -> Result {
if let Some(ref to) = tx.to {
if let NameOrAddress::Name(ens_name) = to {
// resolve to an address
@@ -276,27 +276,22 @@ impl Provider {
}
}
- let tx_hash = self
+ Ok(self
.0
.request("eth_sendTransaction", [tx])
.await
- .map_err(Into::into)?;
- Ok(PendingTransaction::new(tx_hash, self))
+ .map_err(Into::into)?)
}
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash
/// This will consume gas from the account that signed the transaction.
- pub async fn send_raw_transaction(
- &self,
- tx: &Transaction,
- ) -> Result, ProviderError> {
+ pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result {
let rlp = utils::serialize(&tx.rlp());
- let tx_hash = self
+ Ok(self
.0
.request("eth_sendRawTransaction", [rlp])
.await
- .map_err(Into::into)?;
- Ok(PendingTransaction::new(tx_hash, self))
+ .map_err(Into::into)?)
}
/// Signs data using a specific account. This account needs to be unlocked.
@@ -332,14 +327,17 @@ impl Provider {
) -> Result + '_, ProviderError> {
let id = self.new_filter(FilterKind::Logs(filter)).await?;
let fut = move || Box::pin(self.get_filter_changes(id));
- Ok(FilterWatcher::new(id, fut))
+ let filter = FilterWatcher::new(id, fut).interval(self.get_interval());
+
+ Ok(filter)
}
/// Streams new block hashes
pub async fn watch_blocks(&self) -> Result + '_, ProviderError> {
let id = self.new_filter(FilterKind::NewBlocks).await?;
let fut = move || Box::pin(self.get_filter_changes(id));
- Ok(FilterWatcher::new(id, fut))
+ let filter = FilterWatcher::new(id, fut).interval(self.get_interval());
+ Ok(filter)
}
/// Streams pending transactions
@@ -348,7 +346,8 @@ impl Provider {
) -> Result + '_, ProviderError> {
let id = self.new_filter(FilterKind::PendingTransactions).await?;
let fut = move || Box::pin(self.get_filter_changes(id));
- Ok(FilterWatcher::new(id, fut))
+ let filter = FilterWatcher::new(id, fut).interval(self.get_interval());
+ Ok(filter)
}
/// Creates a filter object, based on filter options, to notify when the state changes (logs).
@@ -495,6 +494,25 @@ impl Provider {
self.1 = Some(ens.into());
self
}
+
+ /// Sets the default polling interval for event filters and pending transactions
+ /// (default: 7 seconds)
+ pub fn interval>(mut self, interval: T) -> Self {
+ self.2 = Some(interval.into());
+ self
+ }
+
+ /// Gets the polling interval which the provider currently uses for event filters
+ /// and pending transactions (default: 7 seconds)
+ pub fn get_interval(&self) -> Duration {
+ self.2.unwrap_or(DEFAULT_POLL_INTERVAL)
+ }
+
+ /// Helper which creates a pending transaction object from a transaction hash
+ /// using the provider's polling interval
+ pub fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, P> {
+ PendingTransaction::new(tx_hash, self).interval(self.get_interval())
+ }
}
/// infallbile conversion of Bytes to Address/String
@@ -512,7 +530,7 @@ impl TryFrom<&str> for Provider {
type Error = ParseError;
fn try_from(src: &str) -> Result {
- Ok(Provider(HttpProvider::new(Url::parse(src)?), None))
+ Ok(Provider(HttpProvider::new(Url::parse(src)?), None, None))
}
}
@@ -578,15 +596,12 @@ mod tests {
async fn test_new_block_filter() {
let num_blocks = 3;
- let provider = Provider::::try_from("http://localhost:8545").unwrap();
+ let provider = Provider::::try_from("http://localhost:8545")
+ .unwrap()
+ .interval(Duration::from_millis(1000));
let start_block = provider.get_block_number().await.unwrap();
- let stream = provider
- .watch_blocks()
- .await
- .unwrap()
- .interval(1000u64)
- .stream();
+ let stream = provider.watch_blocks().await.unwrap().stream();
let hashes: Vec = stream.take(num_blocks).collect::>().await;
for (i, hash) in hashes.iter().enumerate() {
@@ -606,14 +621,15 @@ mod tests {
async fn test_new_pending_txs_filter() {
let num_txs = 5;
- let provider = Provider::::try_from("http://localhost:8545").unwrap();
+ let provider = Provider::::try_from("http://localhost:8545")
+ .unwrap()
+ .interval(Duration::from_millis(1000));
let accounts = provider.get_accounts().await.unwrap();
let stream = provider
.watch_pending_transactions()
.await
.unwrap()
- .interval(1000u64)
.stream();
let mut tx_hashes = Vec::new();
diff --git a/ethers-providers/src/stream.rs b/ethers-providers/src/stream.rs
index 6f5e3afb..25ac1098 100644
--- a/ethers-providers/src/stream.rs
+++ b/ethers-providers/src/stream.rs
@@ -20,7 +20,8 @@ pub fn interval(duration: Duration) -> impl Stream- + Send + Unpin {
stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
}
-pub const DEFAULT_POLL_DURATION: Duration = Duration::from_millis(7000);
+/// The default polling interval for filters and pending transactions
+pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
/// Trait for streaming filters.
pub trait FilterStream: StreamExt + Stream
-
@@ -31,7 +32,7 @@ where
fn id(&self) -> U256;
/// Sets the stream's polling interval
- fn interval>(self, duration: T) -> Self;
+ fn interval(self, duration: Duration) -> Self;
/// Alias for Box::pin, must be called in order to pin the stream and be able
/// to call `next` on it.
@@ -73,7 +74,7 @@ where
pub fn new>(id: T, factory: F) -> Self {
Self {
id: id.into(),
- interval: Box::new(interval(DEFAULT_POLL_DURATION)),
+ interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
state: FilterWatcherState::WaitForInterval,
factory,
}
@@ -90,8 +91,8 @@ where
self.id
}
- fn interval>(mut self, duration: T) -> Self {
- self.interval = Box::new(interval(Duration::from_millis(duration.into())));
+ fn interval(mut self, duration: Duration) -> Self {
+ self.interval = Box::new(interval(duration));
self
}
}
@@ -191,7 +192,10 @@ mod watch {
let filter = FilterWatcher::<_, u64>::new(1, factory);
// stream combinator calls are still doable since FilterStream extends
// Stream and StreamExt
- let mut stream = filter.interval(100u64).stream().map(|x| 2 * x);
+ let mut stream = filter
+ .interval(Duration::from_millis(100u64))
+ .stream()
+ .map(|x| 2 * x);
assert_eq!(stream.next().await.unwrap(), 2);
assert_eq!(stream.next().await.unwrap(), 4);
assert_eq!(stream.next().await.unwrap(), 6);
diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs
index 19c873f2..58d5ee93 100644
--- a/ethers-providers/tests/provider.rs
+++ b/ethers-providers/tests/provider.rs
@@ -1,6 +1,6 @@
#![allow(unused_braces)]
use ethers::providers::{Http, Provider};
-use std::convert::TryFrom;
+use std::{convert::TryFrom, time::Duration};
#[cfg(not(feature = "celo"))]
mod eth_tests {
@@ -48,14 +48,9 @@ mod eth_tests {
let (ws, _) = async_tungstenite::tokio::connect_async("ws://localhost:8545")
.await
.unwrap();
- let provider = Provider::new(Ws::new(ws));
+ let provider = Provider::new(Ws::new(ws)).interval(Duration::from_millis(500u64));
- let stream = provider
- .watch_blocks()
- .await
- .unwrap()
- .interval(2000u64)
- .stream();
+ let stream = provider.watch_blocks().await.unwrap().stream();
let _blocks = stream.take(3usize).collect::>().await;
let _number = provider.get_block_number().await.unwrap();
@@ -65,7 +60,9 @@ mod eth_tests {
#[serial]
async fn pending_txs_with_confirmations_ganache() {
let _ganache = Ganache::new().block_time(2u64).spawn();
- let provider = Provider::::try_from("http://localhost:8545").unwrap();
+ let provider = Provider::::try_from("http://localhost:8545")
+ .unwrap()
+ .interval(Duration::from_millis(500u64));
generic_pending_txs_test(provider).await;
}
@@ -84,12 +81,12 @@ mod eth_tests {
let accounts = provider.get_accounts().await.unwrap();
let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]);
- let pending_tx = provider.send_transaction(tx).await.unwrap();
- let hash = *pending_tx;
- let receipt = pending_tx.interval(500u64).confirmations(5).await.unwrap();
+ let tx_hash = provider.send_transaction(tx).await.unwrap();
+ let pending_tx = provider.pending_transaction(tx_hash);
+ let receipt = pending_tx.confirmations(5).await.unwrap();
// got the correct receipt
- assert_eq!(receipt.transaction_hash, hash);
+ assert_eq!(receipt.transaction_hash, tx_hash);
}
}
@@ -117,15 +114,11 @@ mod celo_tests {
#[tokio::test]
async fn watch_blocks() {
- let provider =
- Provider::::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
-
- let stream = provider
- .watch_blocks()
- .await
+ let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org")
.unwrap()
- .interval(2000u64)
- .stream();
+ .interval(Duration::from_millis(2000u64));
+
+ let stream = provider.watch_blocks().await.unwrap().stream();
let _blocks = stream.take(3usize).collect::>().await;
}
diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs
index b6278347..a6858a69 100644
--- a/ethers-signers/src/client.rs
+++ b/ethers-signers/src/client.rs
@@ -1,12 +1,12 @@
use crate::Signer;
use ethers_core::types::{
- Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest,
+ Address, BlockNumber, Bytes, NameOrAddress, Signature, TransactionRequest, TxHash,
};
-use ethers_providers::{JsonRpcClient, PendingTransaction, Provider, ProviderError};
+use ethers_providers::{JsonRpcClient, Provider, ProviderError};
use futures_util::{future::ok, join};
-use std::{future::Future, ops::Deref};
+use std::{future::Future, ops::Deref, time::Duration};
use thiserror::Error;
#[derive(Clone, Debug)]
@@ -42,14 +42,11 @@ use thiserror::Error;
/// let signed_msg = client.provider().sign(b"hello".to_vec(), &client.address()).await?;
///
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
-/// let pending_tx = client.send_transaction(tx, None).await?;
+/// let tx_hash = client.send_transaction(tx, None).await?;
///
-/// // You can get the transaction hash by dereferencing it
-/// let tx_hash = *pending_tx;
-///
-/// // Or you can `await` on the pending transaction to get the receipt with a pre-specified
+/// // You can `await` on the pending transaction to get the receipt with a pre-specified
/// // number of confirmations
-/// let receipt = pending_tx.confirmations(6).await?;
+/// let receipt = client.pending_transaction(tx_hash).confirmations(6).await?;
///
/// // You can connect with other wallets at runtime via the `with_signer` function
/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
@@ -124,7 +121,7 @@ where
&self,
mut tx: TransactionRequest,
block: Option,
- ) -> Result, ClientError> {
+ ) -> Result {
if let Some(ref to) = tx.to {
if let NameOrAddress::Name(ens_name) = to {
let addr = self.resolve_name(&ens_name).await?;
@@ -215,10 +212,30 @@ where
/// Sets the address which will be used for interacting with the blockchain.
/// Useful if no signer is set and you want to specify a default sender for
/// your transactions
+ ///
+ /// # Panics
+ ///
+ /// If the signer is Some. It is forbidden to switch the sender if a private
+ /// key is already specified.
pub fn with_sender>(mut self, address: T) -> Self {
+ if self.signer.is_some() {
+ panic!(
+ "It is forbidden to switch the sender if a signer is specified.
+ Consider using the `with_signer` method if you want to specify a
+ different signer"
+ )
+ }
+
self.address = address.into();
self
}
+
+ /// Sets the default polling interval for event filters and pending transactions
+ pub fn interval>(mut self, interval: T) -> Self {
+ let provider = self.provider.interval(interval.into());
+ self.provider = provider;
+ self
+ }
}
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index 8a74a3a3..186e8533 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -24,10 +24,10 @@
//! .value(10000);
//!
//! // send it! (this will resolve the ENS name to an address under the hood)
-//! let pending_tx = client.send_transaction(tx, None).await?;
+//! let tx_hash = client.send_transaction(tx, None).await?;
//!
//! // get the receipt
-//! let receipt = pending_tx.await?;
+//! let receipt = client.pending_transaction(tx_hash).await?;
//!
//! // get the mined tx
//! let tx = client.get_transaction(receipt.transaction_hash).await?;
diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs
index 3c87804a..62a9735d 100644
--- a/ethers-signers/tests/signer.rs
+++ b/ethers-signers/tests/signer.rs
@@ -3,7 +3,7 @@ use ethers::{
signers::Wallet,
types::TransactionRequest,
};
-use std::convert::TryFrom;
+use std::{convert::TryFrom, time::Duration};
#[cfg(not(feature = "celo"))]
mod eth_tests {
@@ -18,7 +18,8 @@ mod eth_tests {
let provider = Provider::::try_from(
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
)
- .unwrap();
+ .unwrap()
+ .interval(Duration::from_millis(2000u64));
// pls do not drain this key :)
// note: this works even if there's no EIP-155 configured!
@@ -28,15 +29,18 @@ mod eth_tests {
.connect(provider);
let tx = TransactionRequest::pay(client.address(), parse_ether(1u64).unwrap());
- let pending_tx = client
+ let tx_hash = client
.send_transaction(tx, Some(BlockNumber::Pending))
.await
.unwrap();
- let hash = *pending_tx;
- let receipt = pending_tx.confirmations(3).await.unwrap();
+ let receipt = client
+ .pending_transaction(tx_hash)
+ .confirmations(3)
+ .await
+ .unwrap();
// got the correct receipt
- assert_eq!(receipt.transaction_hash, hash);
+ assert_eq!(receipt.transaction_hash, tx_hash);
}
#[tokio::test]
@@ -54,7 +58,9 @@ mod eth_tests {
.unwrap();
// connect to the network
- let provider = Provider::::try_from(url.as_str()).unwrap();
+ let provider = Provider::::try_from(url.as_str())
+ .unwrap()
+ .interval(Duration::from_millis(10u64));
// connect the wallet to the provider
let client = wallet.connect(provider);
@@ -83,8 +89,9 @@ mod celo_tests {
#[tokio::test]
async fn test_send_transaction() {
// Celo testnet
- let provider =
- Provider::::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
+ let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org")
+ .unwrap()
+ .interval(Duration::from_millis(3000u64));
// Funded with https://celo.org/developers/faucet
// Please do not drain this account :)
@@ -95,8 +102,12 @@ mod celo_tests {
let balance_before = client.get_balance(client.address(), None).await.unwrap();
let tx = TransactionRequest::pay(client.address(), 100);
- let pending_tx = client.send_transaction(tx, None).await.unwrap();
- let _receipt = pending_tx.confirmations(3).await.unwrap();
+ let tx_hash = client.send_transaction(tx, None).await.unwrap();
+ let _receipt = client
+ .pending_transaction(tx_hash)
+ .confirmations(3)
+ .await
+ .unwrap();
let balance_after = client.get_balance(client.address(), None).await.unwrap();
assert!(balance_before > balance_after);
}
diff --git a/ethers/examples/contract.rs b/ethers/examples/contract.rs
index 8dde1a29..f08e875f 100644
--- a/ethers/examples/contract.rs
+++ b/ethers/examples/contract.rs
@@ -3,7 +3,7 @@ use ethers::{
prelude::*,
utils::{Ganache, Solc},
};
-use std::convert::TryFrom;
+use std::{convert::TryFrom, sync::Arc, time::Duration};
// Generate the type-safe contract bindings by providing the ABI
abigen!(
@@ -32,13 +32,18 @@ async fn main() -> Result<()> {
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::()?;
// 4. connect to the network
- let provider = Provider::::try_from(url.as_str())?;
+ let provider = Provider::::try_from(url.as_str())?.interval(Duration::from_millis(10u64));
// 5. instantiate the client with the wallet
let client = wallet.connect(provider);
+ let client = Arc::new(client);
// 6. create a factory which will be used to deploy instances of the contract
- let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), &client);
+ let factory = ContractFactory::new(
+ contract.abi.clone(),
+ contract.bytecode.clone(),
+ client.clone(),
+ );
// 7. deploy it with the constructor arguments
let contract = factory.deploy("initial value".to_string())?.send().await?;
@@ -47,10 +52,11 @@ async fn main() -> Result<()> {
let addr = contract.address();
// 9. instantiate the contract
- let contract = SimpleContract::new(addr, &client);
+ let contract = SimpleContract::new(addr, client.clone());
// 10. call the `setValue` method
- let _tx_hash = contract.set_value("hi".to_owned()).send().await?;
+ let tx_hash = contract.set_value("hi".to_owned()).send().await?;
+ let _receipt = client.pending_transaction(tx_hash).await?;
// 11. get all events
let logs = contract
diff --git a/ethers/examples/ens.rs b/ethers/examples/ens.rs
index f721c520..9ee7e192 100644
--- a/ethers/examples/ens.rs
+++ b/ethers/examples/ens.rs
@@ -18,9 +18,9 @@ async fn main() -> Result<()> {
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
// send it!
- let pending_tx = client.send_transaction(tx, None).await?;
+ let tx_hash = client.send_transaction(tx, None).await?;
- let receipt = pending_tx.await?;
+ let receipt = client.pending_transaction(tx_hash).await?;
let tx = client.get_transaction(receipt.transaction_hash).await?;
println!("{}", serde_json::to_string(&tx)?);
diff --git a/ethers/examples/local_signer.rs b/ethers/examples/local_signer.rs
index 43ce7b7b..ba583a05 100644
--- a/ethers/examples/local_signer.rs
+++ b/ethers/examples/local_signer.rs
@@ -27,10 +27,10 @@ async fn main() -> Result<()> {
.value(10000);
// send it!
- let pending_tx = client.send_transaction(tx, None).await?;
+ let tx_hash = client.send_transaction(tx, None).await?;
// get the mined tx
- let receipt = pending_tx.await?;
+ let receipt = client.pending_transaction(tx_hash).await?;
let tx = client.get_transaction(receipt.transaction_hash).await?;
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
diff --git a/ethers/examples/transfer_eth.rs b/ethers/examples/transfer_eth.rs
index e1316dc3..b02dc414 100644
--- a/ethers/examples/transfer_eth.rs
+++ b/ethers/examples/transfer_eth.rs
@@ -22,9 +22,9 @@ async fn main() -> Result<()> {
let balance_before = provider.get_balance(from, None).await?;
// broadcast it via the eth_sendTransaction API
- let pending_tx = provider.send_transaction(tx).await?;
+ let tx_hash = provider.send_transaction(tx).await?;
- let tx = pending_tx.await?;
+ let tx = provider.pending_transaction(tx_hash).await?;
println!("{}", serde_json::to_string(&tx)?);
diff --git a/ethers/examples/watch_blocks.rs b/ethers/examples/watch_blocks.rs
index bc65b138..97c2e008 100644
--- a/ethers/examples/watch_blocks.rs
+++ b/ethers/examples/watch_blocks.rs
@@ -1,10 +1,11 @@
use ethers::prelude::*;
+use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let ws = Ws::connect("ws://localhost:8546").await?;
- let provider = Provider::new(ws);
- let mut stream = provider.watch_blocks().await?.interval(2000u64).stream();
+ let provider = Provider::new(ws).interval(Duration::from_millis(2000));
+ let mut stream = provider.watch_blocks().await?.stream();
while let Some(block) = stream.next().await {
dbg!(block);
}