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()));
|
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
|
||||||
|
|
||||||
// 0. Imports
|
// 0. Imports
|
||||||
let imports = common::imports();
|
let imports = common::imports(&name.to_string());
|
||||||
|
|
||||||
// 1. Declare Contract struct
|
// 1. Declare Contract struct
|
||||||
let struct_decl = common::struct_declaration(&cx, &abi_name);
|
let struct_decl = common::struct_declaration(&cx, &abi_name);
|
||||||
|
@ -67,12 +67,12 @@ impl Context {
|
||||||
|
|
||||||
#struct_decl
|
#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`
|
/// Creates a new contract instance with the specified `ethers`
|
||||||
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
||||||
/// object
|
/// object
|
||||||
pub fn new<T: Into<Address>>(address: T, client: &'a Client<P, S>) -> Self {
|
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);
|
let contract = Contract::new(address.into(), #abi_name.clone(), client.into());
|
||||||
Self(contract)
|
Self(contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use super::Context;
|
use super::{util, Context};
|
||||||
|
|
||||||
use ethers_core::types::Address;
|
use ethers_core::types::Address;
|
||||||
use proc_macro2::{Literal, TokenStream};
|
use proc_macro2::{Literal, TokenStream};
|
||||||
use quote::quote;
|
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! {
|
quote! {
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
// TODO: Can we make this context aware so that it imports either ethers_contract
|
#doc
|
||||||
// or ethers::contract?
|
|
||||||
|
use std::sync::Arc;
|
||||||
use ethers::{
|
use ethers::{
|
||||||
core::{
|
core::{
|
||||||
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
||||||
|
@ -33,17 +36,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
|
||||||
|
|
||||||
// Struct declaration
|
// Struct declaration
|
||||||
#[derive(Clone)]
|
#[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
|
// 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> {
|
impl<P, S> std::ops::Deref for #name<P, S> {
|
||||||
type Target = Contract<'a, P, S>;
|
type Target = Contract<P, S>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.0 }
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
f.debug_tuple(stringify!(#name))
|
f.debug_tuple(stringify!(#name))
|
||||||
.field(&self.address())
|
.field(&self.address())
|
||||||
|
|
|
@ -45,9 +45,9 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
|
||||||
StateMutability::Nonpayable | StateMutability::Payable
|
StateMutability::Nonpayable | StateMutability::Payable
|
||||||
);
|
);
|
||||||
let result = if !is_mutable {
|
let result = if !is_mutable {
|
||||||
quote! { ContractCall<'a, P, S, #outputs> }
|
quote! { ContractCall<P, S, #outputs> }
|
||||||
} else {
|
} else {
|
||||||
quote! { ContractCall<'a, P, S, H256> }
|
quote! { ContractCall<P, S, H256> }
|
||||||
};
|
};
|
||||||
|
|
||||||
let arg = expand_inputs_call_arg(&function.inputs);
|
let arg = expand_inputs_call_arg(&function.inputs);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, Error as AbiError, Function, InvalidOutputType},
|
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 ethers_signers::{Client, ClientError, Signer};
|
||||||
|
|
||||||
use std::{fmt::Debug, marker::PhantomData};
|
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
|
@ -42,18 +42,18 @@ pub enum ContractError {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
|
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
|
||||||
/// Helper for managing a transaction before submitting it to a node
|
/// 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
|
/// The raw transaction object
|
||||||
pub tx: TransactionRequest,
|
pub tx: TransactionRequest,
|
||||||
/// The ABI of the function being called
|
/// The ABI of the function being called
|
||||||
pub function: Function,
|
pub function: Function,
|
||||||
/// Optional block number to be used when calculating the transaction's gas and nonce
|
/// Optional block number to be used when calculating the transaction's gas and nonce
|
||||||
pub block: Option<BlockNumber>,
|
pub block: Option<BlockNumber>,
|
||||||
pub(crate) client: &'a Client<P, S>,
|
pub(crate) client: Arc<Client<P, S>>,
|
||||||
pub(crate) datatype: PhantomData<D>,
|
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
|
/// Sets the `from` field in the transaction to the provided value
|
||||||
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||||
self.tx.from = Some(from.into());
|
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
|
where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
|
@ -111,7 +111,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs and broadcasts the provided transaction
|
/// 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?)
|
Ok(self.client.send_transaction(self.tx, self.block).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ use super::{call::ContractCall, event::Event};
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
|
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 ethers_signers::{Client, Signer};
|
||||||
|
|
||||||
use rustc_hex::ToHex;
|
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.
|
/// 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
|
/// 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);
|
/// .parse::<Wallet>()?.connect(provider);
|
||||||
///
|
///
|
||||||
/// // create the contract object at the address
|
/// // 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.
|
/// // 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)
|
/// // (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.
|
/// // Non-constant methods are executed via the `send()` call on the method builder.
|
||||||
/// let tx_hash = contract
|
/// let tx_hash = contract
|
||||||
/// .method::<_, H256>("setValue", "hi".to_owned())?
|
/// .method::<_, H256>("setValue", "hi".to_owned())?.send().await?;
|
||||||
/// .send()
|
///
|
||||||
/// .await?;
|
/// // `await`ing on the pending transaction resolves to a transaction receipt
|
||||||
|
/// let receipt = contract.pending_transaction(tx_hash).confirmations(6).await?;
|
||||||
///
|
///
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -116,7 +117,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||||
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
|
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
|
||||||
/// # let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
/// # let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
||||||
/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?.connect(provider);
|
/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?.connect(provider);
|
||||||
/// # let contract = Contract::new(address, abi, &client);
|
/// # let contract = Contract::new(address, abi, client);
|
||||||
///
|
///
|
||||||
/// #[derive(Clone, Debug)]
|
/// #[derive(Clone, Debug)]
|
||||||
/// struct ValueChanged {
|
/// struct ValueChanged {
|
||||||
|
@ -162,8 +163,8 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||||
/// [`event`]: method@crate::Contract::event
|
/// [`event`]: method@crate::Contract::event
|
||||||
/// [`method`]: method@crate::Contract::method
|
/// [`method`]: method@crate::Contract::method
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Contract<'a, P, S> {
|
pub struct Contract<P, S> {
|
||||||
client: &'a Client<P, S>,
|
client: Arc<Client<P, S>>,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
address: Address,
|
address: Address,
|
||||||
|
|
||||||
|
@ -174,17 +175,17 @@ pub struct Contract<'a, P, S> {
|
||||||
methods: HashMap<Selector, (String, usize)>,
|
methods: HashMap<Selector, (String, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, P, S> Contract<'a, P, S>
|
impl<P, S> Contract<P, S>
|
||||||
where
|
where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
{
|
{
|
||||||
/// Creates a new contract from the provided client, abi and address
|
/// 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());
|
let methods = create_mapping(&abi.functions, |function| function.selector());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client,
|
client: client.into(),
|
||||||
abi,
|
abi,
|
||||||
address,
|
address,
|
||||||
methods,
|
methods,
|
||||||
|
@ -212,7 +213,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
args: T,
|
args: T,
|
||||||
) -> Result<ContractCall<'a, P, S, D>, Error> {
|
) -> Result<ContractCall<P, S, D>, Error> {
|
||||||
// get the function
|
// get the function
|
||||||
let function = self.abi.function(name)?;
|
let function = self.abi.function(name)?;
|
||||||
self.method_func(function, args)
|
self.method_func(function, args)
|
||||||
|
@ -224,7 +225,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
signature: Selector,
|
signature: Selector,
|
||||||
args: T,
|
args: T,
|
||||||
) -> Result<ContractCall<'a, P, S, D>, Error> {
|
) -> Result<ContractCall<P, S, D>, Error> {
|
||||||
let function = self
|
let function = self
|
||||||
.methods
|
.methods
|
||||||
.get(&signature)
|
.get(&signature)
|
||||||
|
@ -237,7 +238,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
function: &Function,
|
function: &Function,
|
||||||
args: T,
|
args: T,
|
||||||
) -> Result<ContractCall<'a, P, S, D>, Error> {
|
) -> Result<ContractCall<P, S, D>, Error> {
|
||||||
// create the calldata
|
// create the calldata
|
||||||
let data = function.encode_input(&args.into_tokens())?;
|
let data = function.encode_input(&args.into_tokens())?;
|
||||||
|
|
||||||
|
@ -250,7 +251,7 @@ where
|
||||||
|
|
||||||
Ok(ContractCall {
|
Ok(ContractCall {
|
||||||
tx,
|
tx,
|
||||||
client: self.client,
|
client: Arc::clone(&self.client), // cheap clone behind the Arc
|
||||||
block: None,
|
block: None,
|
||||||
function: function.to_owned(),
|
function: function.to_owned(),
|
||||||
datatype: PhantomData,
|
datatype: PhantomData,
|
||||||
|
@ -272,7 +273,7 @@ where
|
||||||
/// Returns a new contract instance using the provided client
|
/// Returns a new contract instance using the provided client
|
||||||
///
|
///
|
||||||
/// Clones `self` internally
|
/// Clones `self` internally
|
||||||
pub fn connect(&self, client: &'a Client<P, S>) -> Self
|
pub fn connect(&self, client: Arc<Client<P, S>>) -> Self
|
||||||
where
|
where
|
||||||
P: Clone,
|
P: Clone,
|
||||||
{
|
{
|
||||||
|
@ -295,6 +296,12 @@ where
|
||||||
pub fn client(&self) -> &Client<P, S> {
|
pub fn client(&self) -> &Client<P, S> {
|
||||||
&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
|
/// 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_providers::JsonRpcClient;
|
||||||
use ethers_signers::{Client, Signer};
|
use ethers_signers::{Client, Signer};
|
||||||
|
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Helper which manages the deployment transaction of a smart contract
|
/// 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
|
/// The deployer's transaction, exposed for overriding the defaults
|
||||||
pub tx: TransactionRequest,
|
pub tx: TransactionRequest,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
client: &'a Client<P, S>,
|
client: Arc<Client<P, S>>,
|
||||||
confs: usize,
|
confs: usize,
|
||||||
block: BlockNumber,
|
block: BlockNumber,
|
||||||
|
interval: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, P, S> Deployer<'a, P, S>
|
impl<P, S> Deployer<P, S>
|
||||||
where
|
where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
|
@ -29,6 +32,12 @@ where
|
||||||
self
|
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 {
|
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.block = block.into();
|
self.block = block.into();
|
||||||
self
|
self
|
||||||
|
@ -37,13 +46,17 @@ where
|
||||||
/// Broadcasts the contract deployment transaction and after waiting for it to
|
/// Broadcasts the contract deployment transaction and after waiting for it to
|
||||||
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
|
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
|
||||||
/// struct at the deployed contract's address.
|
/// struct at the deployed contract's address.
|
||||||
pub async fn send(self) -> Result<Contract<'a, P, S>, ContractError> {
|
pub async fn send(self) -> Result<Contract<P, S>, ContractError> {
|
||||||
let pending_tx = self
|
let tx_hash = self
|
||||||
.client
|
.client
|
||||||
.send_transaction(self.tx, Some(self.block))
|
.send_transaction(self.tx, Some(self.block))
|
||||||
.await?;
|
.await?;
|
||||||
|
let receipt = self
|
||||||
let receipt = pending_tx.confirmations(self.confs).await?;
|
.client
|
||||||
|
.pending_transaction(tx_hash)
|
||||||
|
.interval(self.interval)
|
||||||
|
.confirmations(self.confs)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let address = receipt
|
let address = receipt
|
||||||
.contract_address
|
.contract_address
|
||||||
|
@ -97,7 +110,7 @@ where
|
||||||
/// .parse::<Wallet>()?.connect(provider);
|
/// .parse::<Wallet>()?.connect(provider);
|
||||||
///
|
///
|
||||||
/// // create a factory which will be used to deploy instances of the contract
|
/// // 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
|
/// // The deployer created by the `deploy` call exposes a builder which gets consumed
|
||||||
/// // by the async `send` call
|
/// // by the async `send` call
|
||||||
|
@ -109,13 +122,13 @@ where
|
||||||
/// println!("{}", contract.address());
|
/// println!("{}", contract.address());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
pub struct ContractFactory<'a, P, S> {
|
pub struct ContractFactory<P, S> {
|
||||||
client: &'a Client<P, S>,
|
client: Arc<Client<P, S>>,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
bytecode: Bytes,
|
bytecode: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, P, S> ContractFactory<'a, P, S>
|
impl<P, S> ContractFactory<P, S>
|
||||||
where
|
where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
|
@ -123,9 +136,9 @@ where
|
||||||
/// Creates a factory for deployment of the Contract with bytecode, and the
|
/// 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
|
/// constructor defined in the abi. The client will be used to send any deployment
|
||||||
/// transaction.
|
/// 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 {
|
Self {
|
||||||
client,
|
client: client.into(),
|
||||||
abi,
|
abi,
|
||||||
bytecode,
|
bytecode,
|
||||||
}
|
}
|
||||||
|
@ -139,10 +152,7 @@ where
|
||||||
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
|
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
|
||||||
/// 1. The default poll duration is 7 seconds.
|
/// 1. The default poll duration is 7 seconds.
|
||||||
/// 1. The default number of confirmations is 1 block.
|
/// 1. The default number of confirmations is 1 block.
|
||||||
pub fn deploy<T: Tokenize>(
|
pub fn deploy<T: Tokenize>(self, constructor_args: T) -> Result<Deployer<P, S>, ContractError> {
|
||||||
self,
|
|
||||||
constructor_args: T,
|
|
||||||
) -> Result<Deployer<'a, P, S>, ContractError> {
|
|
||||||
// Encode the constructor args & concatenate with the bytecode if necessary
|
// Encode the constructor args & concatenate with the bytecode if necessary
|
||||||
let params = constructor_args.into_tokens();
|
let params = constructor_args.into_tokens();
|
||||||
let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
|
let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
|
||||||
|
@ -164,11 +174,12 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Deployer {
|
Ok(Deployer {
|
||||||
client: self.client,
|
client: Arc::clone(&self.client), // cheap clone behind the arc
|
||||||
abi: self.abi,
|
abi: self.abi,
|
||||||
tx,
|
tx,
|
||||||
confs: 1,
|
confs: 1,
|
||||||
block: BlockNumber::Latest,
|
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_core::utils::{Ganache, GanacheInstance, Solc};
|
||||||
use ethers_providers::{Http, Provider};
|
use ethers_providers::{Http, Provider};
|
||||||
use ethers_signers::{Client, Wallet};
|
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
|
// Note: We also provide the `abigen` macro for generating these bindings automatically
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -44,17 +44,19 @@ pub fn compile() -> (Abi, Bytes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// connects the private key to http://localhost:8545
|
/// connects the private key to http://localhost:8545
|
||||||
pub fn connect(private_key: &str) -> Client<Http, Wallet> {
|
pub fn connect(private_key: &str) -> Arc<Client<Http, Wallet>> {
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
private_key.parse::<Wallet>().unwrap().connect(provider)
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(10u64));
|
||||||
|
Arc::new(private_key.parse::<Wallet>().unwrap().connect(provider))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launches a ganache instance and deploys the SimpleStorage contract
|
/// Launches a ganache instance and deploys the SimpleStorage contract
|
||||||
pub async fn deploy<'a>(
|
pub async fn deploy(
|
||||||
client: &'a Client<Http, Wallet>,
|
client: Arc<Client<Http, Wallet>>,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
bytecode: Bytes,
|
bytecode: Bytes,
|
||||||
) -> (GanacheInstance, Contract<'a, Http, Wallet>) {
|
) -> (GanacheInstance, Contract<Http, Wallet>) {
|
||||||
let ganache = Ganache::new()
|
let ganache = Ganache::new()
|
||||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod eth_tests {
|
||||||
utils::Ganache,
|
utils::Ganache,
|
||||||
};
|
};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
|
@ -31,7 +31,7 @@ mod eth_tests {
|
||||||
let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e");
|
let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e");
|
||||||
|
|
||||||
// create a factory which will be used to deploy instances of the contract
|
// 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
|
// `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
|
// (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();
|
let value = get_value.clone().call().await.unwrap();
|
||||||
assert_eq!(value, "initial value");
|
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
|
let _tx_hash = contract
|
||||||
.connect(&client2)
|
.connect(client2.clone())
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
|
@ -59,7 +61,7 @@ mod eth_tests {
|
||||||
|
|
||||||
// we can also call contract methods at other addresses with the `at` call
|
// we can also call contract methods at other addresses with the `at` call
|
||||||
// (useful when interacting with multiple ERC20s for example)
|
// (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 contract2 = contract.at(contract2_addr);
|
||||||
let init_value: String = contract2
|
let init_value: String = contract2
|
||||||
.method::<_, String>("getValue", ())
|
.method::<_, String>("getValue", ())
|
||||||
|
@ -82,7 +84,7 @@ mod eth_tests {
|
||||||
async fn get_past_events() {
|
async fn get_past_events() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
|
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`
|
// make a call with `client2`
|
||||||
let _tx_hash = contract
|
let _tx_hash = contract
|
||||||
|
@ -111,7 +113,7 @@ mod eth_tests {
|
||||||
async fn watch_events() {
|
async fn watch_events() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
|
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:
|
// We spawn the event listener:
|
||||||
let mut stream = contract
|
let mut stream = contract
|
||||||
|
@ -125,12 +127,13 @@ mod eth_tests {
|
||||||
|
|
||||||
// and we make a few calls
|
// and we make a few calls
|
||||||
for i in 0..num_calls {
|
for i in 0..num_calls {
|
||||||
let _tx_hash = contract
|
let tx_hash = contract
|
||||||
.method::<_, H256>("setValue", i.to_string())
|
.method::<_, H256>("setValue", i.to_string())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..num_calls {
|
for i in 0..num_calls {
|
||||||
|
@ -144,22 +147,23 @@ mod eth_tests {
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn signer_on_node() {
|
async fn signer_on_node() {
|
||||||
let (abi, bytecode) = compile();
|
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"
|
let deployer = "3cDB3d9e1B74692Bb1E3bb5fc81938151cA64b02"
|
||||||
.parse::<Address>()
|
.parse::<Address>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client = Client::from(provider).with_sender(deployer);
|
let client = Arc::new(Client::from(provider).with_sender(deployer));
|
||||||
let (_ganache, contract) = deploy(&client, abi, bytecode).await;
|
let (_ganache, contract) = deploy(client, abi, bytecode).await;
|
||||||
|
|
||||||
// make a call without the signer
|
// make a call without the signer
|
||||||
let _tx = contract
|
let tx_hash = contract
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
||||||
let value: String = contract
|
let value: String = contract
|
||||||
.method::<_, String>("getValue", ())
|
.method::<_, String>("getValue", ())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -178,7 +182,7 @@ mod celo_tests {
|
||||||
signers::Wallet,
|
signers::Wallet,
|
||||||
types::BlockNumber,
|
types::BlockNumber,
|
||||||
};
|
};
|
||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn deploy_and_call_contract() {
|
async fn deploy_and_call_contract() {
|
||||||
|
@ -192,9 +196,11 @@ mod celo_tests {
|
||||||
let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
||||||
.parse::<Wallet>()
|
.parse::<Wallet>()
|
||||||
.unwrap()
|
.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 deployer = factory.deploy("initial value".to_string()).unwrap();
|
||||||
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
|
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
|
||||||
|
|
||||||
|
@ -207,13 +213,13 @@ mod celo_tests {
|
||||||
assert_eq!(value, "initial value");
|
assert_eq!(value, "initial value");
|
||||||
|
|
||||||
// make a state mutating transaction
|
// make a state mutating transaction
|
||||||
let pending_tx = contract
|
let tx_hash = contract
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _receipt = pending_tx.await.unwrap();
|
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
||||||
|
|
||||||
let value: String = contract
|
let value: String = contract
|
||||||
.method("getValue", ())
|
.method("getValue", ())
|
||||||
|
|
|
@ -111,7 +111,7 @@ mod pending_transaction;
|
||||||
pub use pending_transaction::PendingTransaction;
|
pub use pending_transaction::PendingTransaction;
|
||||||
|
|
||||||
mod stream;
|
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`
|
// re-export `StreamExt` so that consumers can call `next()` on the `FilterStream`
|
||||||
// without having to import futures themselves
|
// without having to import futures themselves
|
||||||
pub use futures_util::StreamExt;
|
pub use futures_util::StreamExt;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
stream::{interval, DEFAULT_POLL_DURATION},
|
stream::{interval, DEFAULT_POLL_INTERVAL},
|
||||||
JsonRpcClient, Provider, ProviderError,
|
JsonRpcClient, Provider, ProviderError,
|
||||||
};
|
};
|
||||||
use ethers_core::types::{TransactionReceipt, TxHash, U64};
|
use ethers_core::types::{TransactionReceipt, TxHash, U64};
|
||||||
|
@ -38,7 +38,7 @@ impl<'a, P: JsonRpcClient> PendingTransaction<'a, P> {
|
||||||
confirmations: 1,
|
confirmations: 1,
|
||||||
provider,
|
provider,
|
||||||
state: PendingTxState::GettingReceipt(fut),
|
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
|
/// Sets the polling interval
|
||||||
pub fn interval<T: Into<u64>>(mut self, duration: T) -> Self {
|
pub fn interval<T: Into<Duration>>(mut self, duration: T) -> Self {
|
||||||
self.interval = Box::new(interval(Duration::from_millis(duration.into())));
|
self.interval = Box::new(interval(duration.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ens,
|
ens,
|
||||||
stream::{FilterStream, FilterWatcher},
|
stream::{FilterStream, FilterWatcher, DEFAULT_POLL_INTERVAL},
|
||||||
Http as HttpProvider, JsonRpcClient, PendingTransaction,
|
Http as HttpProvider, JsonRpcClient, PendingTransaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::{ParseError, Url};
|
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
|
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
||||||
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Provider<P>(P, Option<Address>);
|
pub struct Provider<P>(P, Option<Address>, Option<Duration>);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
/// An error thrown when making a call to the provider
|
/// An error thrown when making a call to the provider
|
||||||
|
@ -72,7 +72,7 @@ pub enum FilterKind<'a> {
|
||||||
impl<P: JsonRpcClient> Provider<P> {
|
impl<P: JsonRpcClient> Provider<P> {
|
||||||
/// Instantiate a new provider with a backend.
|
/// Instantiate a new provider with a backend.
|
||||||
pub fn new(provider: P) -> Self {
|
pub fn new(provider: P) -> Self {
|
||||||
Self(provider, None)
|
Self(provider, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
////// Blockchain Status
|
////// Blockchain Status
|
||||||
|
@ -265,7 +265,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
pub async fn send_transaction(
|
pub async fn send_transaction(
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
) -> Result<PendingTransaction<'_, P>, ProviderError> {
|
) -> Result<TxHash, ProviderError> {
|
||||||
if let Some(ref to) = tx.to {
|
if let Some(ref to) = tx.to {
|
||||||
if let NameOrAddress::Name(ens_name) = to {
|
if let NameOrAddress::Name(ens_name) = to {
|
||||||
// resolve to an address
|
// resolve to an address
|
||||||
|
@ -276,27 +276,22 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx_hash = self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
.request("eth_sendTransaction", [tx])
|
.request("eth_sendTransaction", [tx])
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)?;
|
.map_err(Into::into)?)
|
||||||
Ok(PendingTransaction::new(tx_hash, self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash
|
/// 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.
|
/// This will consume gas from the account that signed the transaction.
|
||||||
pub async fn send_raw_transaction(
|
pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, ProviderError> {
|
||||||
&self,
|
|
||||||
tx: &Transaction,
|
|
||||||
) -> Result<PendingTransaction<'_, P>, ProviderError> {
|
|
||||||
let rlp = utils::serialize(&tx.rlp());
|
let rlp = utils::serialize(&tx.rlp());
|
||||||
let tx_hash = self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
.request("eth_sendRawTransaction", [rlp])
|
.request("eth_sendRawTransaction", [rlp])
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)?;
|
.map_err(Into::into)?)
|
||||||
Ok(PendingTransaction::new(tx_hash, self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs data using a specific account. This account needs to be unlocked.
|
/// 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> {
|
) -> Result<impl FilterStream<Log> + '_, ProviderError> {
|
||||||
let id = self.new_filter(FilterKind::Logs(filter)).await?;
|
let id = self.new_filter(FilterKind::Logs(filter)).await?;
|
||||||
let fut = move || Box::pin(self.get_filter_changes(id));
|
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
|
/// Streams new block hashes
|
||||||
pub async fn watch_blocks(&self) -> Result<impl FilterStream<H256> + '_, ProviderError> {
|
pub async fn watch_blocks(&self) -> Result<impl FilterStream<H256> + '_, ProviderError> {
|
||||||
let id = self.new_filter(FilterKind::NewBlocks).await?;
|
let id = self.new_filter(FilterKind::NewBlocks).await?;
|
||||||
let fut = move || Box::pin(self.get_filter_changes(id));
|
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
|
/// Streams pending transactions
|
||||||
|
@ -348,7 +346,8 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
) -> Result<impl FilterStream<H256> + '_, ProviderError> {
|
) -> Result<impl FilterStream<H256> + '_, ProviderError> {
|
||||||
let id = self.new_filter(FilterKind::PendingTransactions).await?;
|
let id = self.new_filter(FilterKind::PendingTransactions).await?;
|
||||||
let fut = move || Box::pin(self.get_filter_changes(id));
|
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).
|
/// 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.1 = Some(ens.into());
|
||||||
self
|
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
|
/// infallbile conversion of Bytes to Address/String
|
||||||
|
@ -512,7 +530,7 @@ impl TryFrom<&str> for Provider<HttpProvider> {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
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() {
|
async fn test_new_block_filter() {
|
||||||
let num_blocks = 3;
|
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 start_block = provider.get_block_number().await.unwrap();
|
||||||
|
|
||||||
let stream = provider
|
let stream = provider.watch_blocks().await.unwrap().stream();
|
||||||
.watch_blocks()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.interval(1000u64)
|
|
||||||
.stream();
|
|
||||||
|
|
||||||
let hashes: Vec<H256> = stream.take(num_blocks).collect::<Vec<H256>>().await;
|
let hashes: Vec<H256> = stream.take(num_blocks).collect::<Vec<H256>>().await;
|
||||||
for (i, hash) in hashes.iter().enumerate() {
|
for (i, hash) in hashes.iter().enumerate() {
|
||||||
|
@ -606,14 +621,15 @@ mod tests {
|
||||||
async fn test_new_pending_txs_filter() {
|
async fn test_new_pending_txs_filter() {
|
||||||
let num_txs = 5;
|
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 accounts = provider.get_accounts().await.unwrap();
|
||||||
|
|
||||||
let stream = provider
|
let stream = provider
|
||||||
.watch_pending_transactions()
|
.watch_pending_transactions()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(1000u64)
|
|
||||||
.stream();
|
.stream();
|
||||||
|
|
||||||
let mut tx_hashes = Vec::new();
|
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)
|
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.
|
/// Trait for streaming filters.
|
||||||
pub trait FilterStream<R>: StreamExt + Stream<Item = R>
|
pub trait FilterStream<R>: StreamExt + Stream<Item = R>
|
||||||
|
@ -31,7 +32,7 @@ where
|
||||||
fn id(&self) -> U256;
|
fn id(&self) -> U256;
|
||||||
|
|
||||||
/// Sets the stream's polling interval
|
/// 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
|
/// Alias for Box::pin, must be called in order to pin the stream and be able
|
||||||
/// to call `next` on it.
|
/// to call `next` on it.
|
||||||
|
@ -73,7 +74,7 @@ where
|
||||||
pub fn new<T: Into<U256>>(id: T, factory: F) -> Self {
|
pub fn new<T: Into<U256>>(id: T, factory: F) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
interval: Box::new(interval(DEFAULT_POLL_DURATION)),
|
interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
|
||||||
state: FilterWatcherState::WaitForInterval,
|
state: FilterWatcherState::WaitForInterval,
|
||||||
factory,
|
factory,
|
||||||
}
|
}
|
||||||
|
@ -90,8 +91,8 @@ where
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interval<T: Into<u64>>(mut self, duration: T) -> Self {
|
fn interval(mut self, duration: Duration) -> Self {
|
||||||
self.interval = Box::new(interval(Duration::from_millis(duration.into())));
|
self.interval = Box::new(interval(duration));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +192,10 @@ mod watch {
|
||||||
let filter = FilterWatcher::<_, u64>::new(1, factory);
|
let filter = FilterWatcher::<_, u64>::new(1, factory);
|
||||||
// stream combinator calls are still doable since FilterStream extends
|
// stream combinator calls are still doable since FilterStream extends
|
||||||
// Stream and StreamExt
|
// 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(), 2);
|
||||||
assert_eq!(stream.next().await.unwrap(), 4);
|
assert_eq!(stream.next().await.unwrap(), 4);
|
||||||
assert_eq!(stream.next().await.unwrap(), 6);
|
assert_eq!(stream.next().await.unwrap(), 6);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![allow(unused_braces)]
|
#![allow(unused_braces)]
|
||||||
use ethers::providers::{Http, Provider};
|
use ethers::providers::{Http, Provider};
|
||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, time::Duration};
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
mod eth_tests {
|
mod eth_tests {
|
||||||
|
@ -48,14 +48,9 @@ mod eth_tests {
|
||||||
let (ws, _) = async_tungstenite::tokio::connect_async("ws://localhost:8545")
|
let (ws, _) = async_tungstenite::tokio::connect_async("ws://localhost:8545")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let provider = Provider::new(Ws::new(ws));
|
let provider = Provider::new(Ws::new(ws)).interval(Duration::from_millis(500u64));
|
||||||
|
|
||||||
let stream = provider
|
let stream = provider.watch_blocks().await.unwrap().stream();
|
||||||
.watch_blocks()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.interval(2000u64)
|
|
||||||
.stream();
|
|
||||||
|
|
||||||
let _blocks = stream.take(3usize).collect::<Vec<H256>>().await;
|
let _blocks = stream.take(3usize).collect::<Vec<H256>>().await;
|
||||||
let _number = provider.get_block_number().await.unwrap();
|
let _number = provider.get_block_number().await.unwrap();
|
||||||
|
@ -65,7 +60,9 @@ mod eth_tests {
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn pending_txs_with_confirmations_ganache() {
|
async fn pending_txs_with_confirmations_ganache() {
|
||||||
let _ganache = Ganache::new().block_time(2u64).spawn();
|
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;
|
generic_pending_txs_test(provider).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +81,12 @@ mod eth_tests {
|
||||||
let accounts = provider.get_accounts().await.unwrap();
|
let accounts = provider.get_accounts().await.unwrap();
|
||||||
|
|
||||||
let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]);
|
let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]);
|
||||||
let pending_tx = provider.send_transaction(tx).await.unwrap();
|
let tx_hash = provider.send_transaction(tx).await.unwrap();
|
||||||
let hash = *pending_tx;
|
let pending_tx = provider.pending_transaction(tx_hash);
|
||||||
let receipt = pending_tx.interval(500u64).confirmations(5).await.unwrap();
|
let receipt = pending_tx.confirmations(5).await.unwrap();
|
||||||
|
|
||||||
// got the correct receipt
|
// 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]
|
#[tokio::test]
|
||||||
async fn watch_blocks() {
|
async fn watch_blocks() {
|
||||||
let provider =
|
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
||||||
Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
|
|
||||||
|
|
||||||
let stream = provider
|
|
||||||
.watch_blocks()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(2000u64)
|
.interval(Duration::from_millis(2000u64));
|
||||||
.stream();
|
|
||||||
|
let stream = provider.watch_blocks().await.unwrap().stream();
|
||||||
|
|
||||||
let _blocks = stream.take(3usize).collect::<Vec<H256>>().await;
|
let _blocks = stream.take(3usize).collect::<Vec<H256>>().await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::Signer;
|
use crate::Signer;
|
||||||
|
|
||||||
use ethers_core::types::{
|
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 futures_util::{future::ok, join};
|
||||||
use std::{future::Future, ops::Deref};
|
use std::{future::Future, ops::Deref, time::Duration};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -42,14 +42,11 @@ use thiserror::Error;
|
||||||
/// let signed_msg = client.provider().sign(b"hello".to_vec(), &client.address()).await?;
|
/// let signed_msg = client.provider().sign(b"hello".to_vec(), &client.address()).await?;
|
||||||
///
|
///
|
||||||
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
|
/// 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
|
/// // You can `await` on the pending transaction to get the receipt with a pre-specified
|
||||||
/// let tx_hash = *pending_tx;
|
|
||||||
///
|
|
||||||
/// // Or you can `await` on the pending transaction to get the receipt with a pre-specified
|
|
||||||
/// // number of confirmations
|
/// // 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
|
/// // You can connect with other wallets at runtime via the `with_signer` function
|
||||||
/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
|
/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
|
||||||
|
@ -124,7 +121,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<PendingTransaction<'_, P>, ClientError> {
|
) -> Result<TxHash, ClientError> {
|
||||||
if let Some(ref to) = tx.to {
|
if let Some(ref to) = tx.to {
|
||||||
if let NameOrAddress::Name(ens_name) = to {
|
if let NameOrAddress::Name(ens_name) = to {
|
||||||
let addr = self.resolve_name(&ens_name).await?;
|
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.
|
/// 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
|
/// Useful if no signer is set and you want to specify a default sender for
|
||||||
/// your transactions
|
/// 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 {
|
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.address = address.into();
|
||||||
self
|
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`
|
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
|
||||||
|
|
|
@ -24,10 +24,10 @@
|
||||||
//! .value(10000);
|
//! .value(10000);
|
||||||
//!
|
//!
|
||||||
//! // send it! (this will resolve the ENS name to an address under the hood)
|
//! // 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
|
//! // get the receipt
|
||||||
//! let receipt = pending_tx.await?;
|
//! let receipt = client.pending_transaction(tx_hash).await?;
|
||||||
//!
|
//!
|
||||||
//! // get the mined tx
|
//! // get the mined tx
|
||||||
//! let tx = client.get_transaction(receipt.transaction_hash).await?;
|
//! let tx = client.get_transaction(receipt.transaction_hash).await?;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ethers::{
|
||||||
signers::Wallet,
|
signers::Wallet,
|
||||||
types::TransactionRequest,
|
types::TransactionRequest,
|
||||||
};
|
};
|
||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, time::Duration};
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
mod eth_tests {
|
mod eth_tests {
|
||||||
|
@ -18,7 +18,8 @@ mod eth_tests {
|
||||||
let provider = Provider::<Http>::try_from(
|
let provider = Provider::<Http>::try_from(
|
||||||
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
|
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(2000u64));
|
||||||
|
|
||||||
// pls do not drain this key :)
|
// pls do not drain this key :)
|
||||||
// note: this works even if there's no EIP-155 configured!
|
// note: this works even if there's no EIP-155 configured!
|
||||||
|
@ -28,15 +29,18 @@ mod eth_tests {
|
||||||
.connect(provider);
|
.connect(provider);
|
||||||
|
|
||||||
let tx = TransactionRequest::pay(client.address(), parse_ether(1u64).unwrap());
|
let tx = TransactionRequest::pay(client.address(), parse_ether(1u64).unwrap());
|
||||||
let pending_tx = client
|
let tx_hash = client
|
||||||
.send_transaction(tx, Some(BlockNumber::Pending))
|
.send_transaction(tx, Some(BlockNumber::Pending))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hash = *pending_tx;
|
let receipt = client
|
||||||
let receipt = pending_tx.confirmations(3).await.unwrap();
|
.pending_transaction(tx_hash)
|
||||||
|
.confirmations(3)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// got the correct receipt
|
// got the correct receipt
|
||||||
assert_eq!(receipt.transaction_hash, hash);
|
assert_eq!(receipt.transaction_hash, tx_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -54,7 +58,9 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// connect to the network
|
// 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
|
// connect the wallet to the provider
|
||||||
let client = wallet.connect(provider);
|
let client = wallet.connect(provider);
|
||||||
|
@ -83,8 +89,9 @@ mod celo_tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_send_transaction() {
|
async fn test_send_transaction() {
|
||||||
// Celo testnet
|
// Celo testnet
|
||||||
let provider =
|
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
||||||
Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(3000u64));
|
||||||
|
|
||||||
// Funded with https://celo.org/developers/faucet
|
// Funded with https://celo.org/developers/faucet
|
||||||
// Please do not drain this account :)
|
// 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 balance_before = client.get_balance(client.address(), None).await.unwrap();
|
||||||
let tx = TransactionRequest::pay(client.address(), 100);
|
let tx = TransactionRequest::pay(client.address(), 100);
|
||||||
let pending_tx = client.send_transaction(tx, None).await.unwrap();
|
let tx_hash = client.send_transaction(tx, None).await.unwrap();
|
||||||
let _receipt = pending_tx.confirmations(3).await.unwrap();
|
let _receipt = client
|
||||||
|
.pending_transaction(tx_hash)
|
||||||
|
.confirmations(3)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
||||||
assert!(balance_before > balance_after);
|
assert!(balance_before > balance_after);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ethers::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::{Ganache, Solc},
|
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
|
// Generate the type-safe contract bindings by providing the ABI
|
||||||
abigen!(
|
abigen!(
|
||||||
|
@ -32,13 +32,18 @@ async fn main() -> Result<()> {
|
||||||
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?;
|
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?;
|
||||||
|
|
||||||
// 4. connect to the network
|
// 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
|
// 5. instantiate the client with the wallet
|
||||||
let client = wallet.connect(provider);
|
let client = wallet.connect(provider);
|
||||||
|
let client = Arc::new(client);
|
||||||
|
|
||||||
// 6. create a factory which will be used to deploy instances of the contract
|
// 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
|
// 7. deploy it with the constructor arguments
|
||||||
let contract = factory.deploy("initial value".to_string())?.send().await?;
|
let contract = factory.deploy("initial value".to_string())?.send().await?;
|
||||||
|
@ -47,10 +52,11 @@ async fn main() -> Result<()> {
|
||||||
let addr = contract.address();
|
let addr = contract.address();
|
||||||
|
|
||||||
// 9. instantiate the contract
|
// 9. instantiate the contract
|
||||||
let contract = SimpleContract::new(addr, &client);
|
let contract = SimpleContract::new(addr, client.clone());
|
||||||
|
|
||||||
// 10. call the `setValue` method
|
// 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
|
// 11. get all events
|
||||||
let logs = contract
|
let logs = contract
|
||||||
|
|
|
@ -18,9 +18,9 @@ async fn main() -> Result<()> {
|
||||||
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
|
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
|
||||||
|
|
||||||
// send it!
|
// 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?;
|
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
||||||
|
|
||||||
println!("{}", serde_json::to_string(&tx)?);
|
println!("{}", serde_json::to_string(&tx)?);
|
||||||
|
|
|
@ -27,10 +27,10 @@ async fn main() -> Result<()> {
|
||||||
.value(10000);
|
.value(10000);
|
||||||
|
|
||||||
// send it!
|
// send it!
|
||||||
let pending_tx = client.send_transaction(tx, None).await?;
|
let tx_hash = client.send_transaction(tx, None).await?;
|
||||||
|
|
||||||
// get the mined tx
|
// 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?;
|
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
||||||
|
|
||||||
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
|
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?;
|
let balance_before = provider.get_balance(from, None).await?;
|
||||||
|
|
||||||
// broadcast it via the eth_sendTransaction API
|
// 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)?);
|
println!("{}", serde_json::to_string(&tx)?);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let ws = Ws::connect("ws://localhost:8546").await?;
|
let ws = Ws::connect("ws://localhost:8546").await?;
|
||||||
let provider = Provider::new(ws);
|
let provider = Provider::new(ws).interval(Duration::from_millis(2000));
|
||||||
let mut stream = provider.watch_blocks().await?.interval(2000u64).stream();
|
let mut stream = provider.watch_blocks().await?.stream();
|
||||||
while let Some(block) = stream.next().await {
|
while let Some(block) = stream.next().await {
|
||||||
dbg!(block);
|
dbg!(block);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue