Replace contract client references with Arc (#35)
* feat(provider): allow specifying a default polling interval param This parameter is going to be used for all subsequent client calls by default. It can still be overriden with the internal `interval` calls * feat(contract): replace reference to Client with Arc * feat(abigen): adjusts codegen to use Arcs * fix(ethers): adjust examples to new apis * fix(provider): return TxHash instead of PendingTransaction on tx submission Returning a PendingTransaction allowed us to have nice ethers.js-like syntax where you submit a transaction and then can immediately await it. Unfortunately, now that we use Arcs and not lifetimes this meant that we would need to bind the function call in a variable, and then await on it, which is pretty bad UX. To fix this, we revert back to returning a TxHash and introduce a convenience method on the provider and the contract which takes a tx_hash and returns a PendingTransaction object. The syntax ends up being slightly more verbose (although more explicit), but the issue is fixed.
This commit is contained in:
parent
7a17e54fbd
commit
1cfbc7b3c3
|
@ -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<P, S> {
|
||||
/// 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<T: Into<Address>>(address: T, client: &'a Client<P, S>) -> Self {
|
||||
let contract = Contract::new(address.into(), #abi_name.clone(), client);
|
||||
pub fn new<T: Into<Address>, C: Into<Arc<Client<P, S>>>>(address: T, client: C) -> Self {
|
||||
let contract = Contract::new(address.into(), #abi_name.clone(), client.into());
|
||||
Self(contract)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<P, S>(Contract<P, S>);
|
||||
|
||||
|
||||
// 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<P, S> std::ops::Deref for #name<P, S> {
|
||||
type Target = Contract<P, S>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<'a, P: JsonRpcClient, S: Signer> std::fmt::Debug for #name<'a, P, S> {
|
||||
impl<P: JsonRpcClient, S: Signer> std::fmt::Debug for #name<P, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_tuple(stringify!(#name))
|
||||
.field(&self.address())
|
||||
|
|
|
@ -45,9 +45,9 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
|
|||
StateMutability::Nonpayable | StateMutability::Payable
|
||||
);
|
||||
let result = if !is_mutable {
|
||||
quote! { ContractCall<'a, P, S, #outputs> }
|
||||
quote! { ContractCall<P, S, #outputs> }
|
||||
} else {
|
||||
quote! { ContractCall<'a, P, S, H256> }
|
||||
quote! { ContractCall<P, S, H256> }
|
||||
};
|
||||
|
||||
let arg = expand_inputs_call_arg(&function.inputs);
|
||||
|
|
|
@ -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<P, S, D> {
|
||||
/// 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>,
|
||||
pub(crate) client: &'a Client<P, S>,
|
||||
pub(crate) client: Arc<Client<P, S>>,
|
||||
pub(crate) datatype: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<'a, P, S, D: Detokenize> ContractCall<'a, P, S, D> {
|
||||
impl<P, S, D: Detokenize> ContractCall<P, S, D> {
|
||||
/// 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());
|
||||
|
@ -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<P, S, D> ContractCall<P, S, D>
|
||||
where
|
||||
S: Signer,
|
||||
P: JsonRpcClient,
|
||||
|
@ -111,7 +111,7 @@ where
|
|||
}
|
||||
|
||||
/// Signs and broadcasts the provided transaction
|
||||
pub async fn send(self) -> Result<PendingTransaction<'a, P>, ContractError> {
|
||||
pub async fn send(self) -> Result<TxHash, ContractError> {
|
||||
Ok(self.client.send_transaction(self.tx, self.block).await?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<Wallet>()?.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::<Http>::try_from("http://localhost:8545").unwrap();
|
||||
/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?.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<P, S>,
|
||||
pub struct Contract<P, S> {
|
||||
client: Arc<Client<P, S>>,
|
||||
abi: Abi,
|
||||
address: Address,
|
||||
|
||||
|
@ -174,17 +175,17 @@ pub struct Contract<'a, P, S> {
|
|||
methods: HashMap<Selector, (String, usize)>,
|
||||
}
|
||||
|
||||
impl<'a, P, S> Contract<'a, P, S>
|
||||
impl<P, S> Contract<P, S>
|
||||
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<P, S>) -> Self {
|
||||
pub fn new(address: Address, abi: Abi, client: impl Into<Arc<Client<P, S>>>) -> 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<ContractCall<'a, P, S, D>, Error> {
|
||||
) -> Result<ContractCall<P, S, D>, 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<ContractCall<'a, P, S, D>, Error> {
|
||||
) -> Result<ContractCall<P, S, D>, Error> {
|
||||
let function = self
|
||||
.methods
|
||||
.get(&signature)
|
||||
|
@ -237,7 +238,7 @@ where
|
|||
&self,
|
||||
function: &Function,
|
||||
args: T,
|
||||
) -> Result<ContractCall<'a, P, S, D>, Error> {
|
||||
) -> Result<ContractCall<P, S, D>, 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<P, S>) -> Self
|
||||
pub fn connect(&self, client: Arc<Client<P, S>>) -> Self
|
||||
where
|
||||
P: Clone,
|
||||
{
|
||||
|
@ -295,6 +296,12 @@ where
|
|||
pub fn client(&self) -> &Client<P, S> {
|
||||
&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
|
||||
|
|
|
@ -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<P, S> {
|
||||
/// The deployer's transaction, exposed for overriding the defaults
|
||||
pub tx: TransactionRequest,
|
||||
abi: Abi,
|
||||
client: &'a Client<P, S>,
|
||||
client: Arc<Client<P, S>>,
|
||||
confs: usize,
|
||||
block: BlockNumber,
|
||||
interval: Duration,
|
||||
}
|
||||
|
||||
impl<'a, P, S> Deployer<'a, P, S>
|
||||
impl<P, S> Deployer<P, S>
|
||||
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<T: Into<Duration>>(mut self, interval: T) -> Self {
|
||||
self.interval = interval.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block<T: Into<BlockNumber>>(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<Contract<'a, P, S>, ContractError> {
|
||||
let pending_tx = self
|
||||
pub async fn send(self) -> Result<Contract<P, S>, 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::<Wallet>()?.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<P, S>,
|
||||
pub struct ContractFactory<P, S> {
|
||||
client: Arc<Client<P, S>>,
|
||||
abi: Abi,
|
||||
bytecode: Bytes,
|
||||
}
|
||||
|
||||
impl<'a, P, S> ContractFactory<'a, P, S>
|
||||
impl<P, S> ContractFactory<P, S>
|
||||
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<P, S>) -> Self {
|
||||
pub fn new(abi: Abi, bytecode: Bytes, client: impl Into<Arc<Client<P, S>>>) -> 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<T: Tokenize>(
|
||||
self,
|
||||
constructor_args: T,
|
||||
) -> Result<Deployer<'a, P, S>, ContractError> {
|
||||
pub fn deploy<T: Tokenize>(self, constructor_args: T) -> Result<Deployer<P, S>, 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Http, Wallet> {
|
||||
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
||||
private_key.parse::<Wallet>().unwrap().connect(provider)
|
||||
pub fn connect(private_key: &str) -> Arc<Client<Http, Wallet>> {
|
||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||
.unwrap()
|
||||
.interval(Duration::from_millis(10u64));
|
||||
Arc::new(private_key.parse::<Wallet>().unwrap().connect(provider))
|
||||
}
|
||||
|
||||
/// Launches a ganache instance and deploys the SimpleStorage contract
|
||||
pub async fn deploy<'a>(
|
||||
client: &'a Client<Http, Wallet>,
|
||||
pub async fn deploy(
|
||||
client: Arc<Client<Http, Wallet>>,
|
||||
abi: Abi,
|
||||
bytecode: Bytes,
|
||||
) -> (GanacheInstance, Contract<'a, Http, Wallet>) {
|
||||
) -> (GanacheInstance, Contract<Http, Wallet>) {
|
||||
let ganache = Ganache::new()
|
||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
||||
.spawn();
|
||||
|
|
|
@ -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::<Http>::try_from("http://localhost:8545").unwrap();
|
||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||
.unwrap()
|
||||
.interval(Duration::from_millis(10u64));
|
||||
let deployer = "3cDB3d9e1B74692Bb1E3bb5fc81938151cA64b02"
|
||||
.parse::<Address>()
|
||||
.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::<Wallet>()
|
||||
.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", ())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<T: Into<u64>>(mut self, duration: T) -> Self {
|
||||
self.interval = Box::new(interval(Duration::from_millis(duration.into())));
|
||||
pub fn interval<T: Into<Duration>>(mut self, duration: T) -> Self {
|
||||
self.interval = Box::new(interval(duration.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>(P, Option<Address>);
|
||||
pub struct Provider<P>(P, Option<Address>, Option<Duration>);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
/// An error thrown when making a call to the provider
|
||||
|
@ -72,7 +72,7 @@ pub enum FilterKind<'a> {
|
|||
impl<P: JsonRpcClient> Provider<P> {
|
||||
/// 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<P: JsonRpcClient> Provider<P> {
|
|||
pub async fn send_transaction(
|
||||
&self,
|
||||
mut tx: TransactionRequest,
|
||||
) -> Result<PendingTransaction<'_, P>, ProviderError> {
|
||||
) -> Result<TxHash, ProviderError> {
|
||||
if let Some(ref to) = tx.to {
|
||||
if let NameOrAddress::Name(ens_name) = to {
|
||||
// resolve to an address
|
||||
|
@ -276,27 +276,22 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
}
|
||||
}
|
||||
|
||||
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<PendingTransaction<'_, P>, ProviderError> {
|
||||
pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, ProviderError> {
|
||||
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<P: JsonRpcClient> Provider<P> {
|
|||
) -> Result<impl FilterStream<Log> + '_, 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<impl FilterStream<H256> + '_, 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<P: JsonRpcClient> Provider<P> {
|
|||
) -> Result<impl FilterStream<H256> + '_, 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<P: JsonRpcClient> Provider<P> {
|
|||
self.1 = Some(ens.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the default polling interval for event filters and pending transactions
|
||||
/// (default: 7 seconds)
|
||||
pub fn interval<T: Into<Duration>>(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<HttpProvider> {
|
|||
type Error = ParseError;
|
||||
|
||||
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
||||
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::<HttpProvider>::try_from("http://localhost:8545").unwrap();
|
||||
let provider = Provider::<HttpProvider>::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<H256> = stream.take(num_blocks).collect::<Vec<H256>>().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::<HttpProvider>::try_from("http://localhost:8545").unwrap();
|
||||
let provider = Provider::<HttpProvider>::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();
|
||||
|
|
|
@ -20,7 +20,8 @@ pub fn interval(duration: Duration) -> impl Stream<Item = ()> + 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<R>: StreamExt + Stream<Item = R>
|
||||
|
@ -31,7 +32,7 @@ where
|
|||
fn id(&self) -> U256;
|
||||
|
||||
/// Sets the stream's polling interval
|
||||
fn interval<T: Into<u64>>(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<T: Into<U256>>(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<T: Into<u64>>(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);
|
||||
|
|
|
@ -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::<Vec<H256>>().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::<Http>::try_from("http://localhost:8545").unwrap();
|
||||
let provider = Provider::<Http>::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::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
|
||||
|
||||
let stream = provider
|
||||
.watch_blocks()
|
||||
.await
|
||||
let provider = Provider::<Http>::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::<Vec<H256>>().await;
|
||||
}
|
||||
|
|
|
@ -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<BlockNumber>,
|
||||
) -> Result<PendingTransaction<'_, P>, ClientError> {
|
||||
) -> Result<TxHash, ClientError> {
|
||||
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<T: Into<Address>>(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<T: Into<Duration>>(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`
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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::<Http>::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::<Http>::try_from(url.as_str()).unwrap();
|
||||
let provider = Provider::<Http>::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::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
|
||||
let provider = Provider::<Http>::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);
|
||||
}
|
||||
|
|
|
@ -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::<Wallet>()?;
|
||||
|
||||
// 4. connect to the network
|
||||
let provider = Provider::<Http>::try_from(url.as_str())?;
|
||||
let provider = Provider::<Http>::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
|
||||
|
|
|
@ -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)?);
|
||||
|
|
|
@ -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)?);
|
||||
|
|
|
@ -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)?);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue