use crate::{ ens, pubsub::{PubsubClient, SubscriptionStream}, stream::{FilterWatcher, DEFAULT_POLL_INTERVAL}, FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction, }; use ethers_core::{ abi::{self, Detokenize, ParamType}, types::{ Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Filter, Log, NameOrAddress, Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, }, utils, }; #[cfg(feature = "celo")] use crate::CeloMiddleware; use crate::Middleware; use async_trait::async_trait; use hex::FromHex; use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use url::{ParseError, Url}; use std::{convert::TryFrom, fmt::Debug, time::Duration}; use tracing::trace; use tracing_futures::Instrument; /// An abstract provider for interacting with the [Ethereum JSON RPC /// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated /// with a data transport which implements the [`JsonRpcClient`](trait@crate::JsonRpcClient) trait /// (e.g. [HTTP](crate::Http), Websockets etc.) /// /// # Example /// /// ```no_run /// # async fn foo() -> Result<(), Box> { /// use ethers::providers::{Middleware, Provider, Http}; /// use std::convert::TryFrom; /// /// let provider = Provider::::try_from( /// "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27" /// ).expect("could not instantiate HTTP Provider"); /// /// let block = provider.get_block(100u64).await?; /// println!("Got block: {}", serde_json::to_string(&block)?); /// # Ok(()) /// # } /// ``` #[derive(Clone, Debug)] // TODO: Convert to proper struct pub struct Provider

(P, Option

, Option, Option
); impl

AsRef

for Provider

{ fn as_ref(&self) -> &P { &self.0 } } impl FromErr for ProviderError { fn from(src: ProviderError) -> Self { src } } #[derive(Debug, Error)] /// An error thrown when making a call to the provider pub enum ProviderError { /// An internal error in the JSON RPC Client #[error(transparent)] JsonRpcClientError(#[from] Box), /// An error during ENS name resolution #[error("ens name not found: {0}")] EnsError(String), #[error(transparent)] SerdeJson(#[from] serde_json::Error), #[error(transparent)] HexError(#[from] hex::FromHexError), #[error("custom error: {0}")] CustomError(String), } /// Types of filters supported by the JSON-RPC. #[derive(Clone, Debug)] pub enum FilterKind<'a> { /// `eth_newBlockFilter` Logs(&'a Filter), /// `eth_newBlockFilter` filter NewBlocks, /// `eth_newPendingTransactionFilter` filter PendingTransactions, } // JSON RPC bindings impl Provider

{ /// Instantiate a new provider with a backend. pub fn new(provider: P) -> Self { Self(provider, None, None, None) } pub fn with_sender(mut self, address: impl Into

) -> Self { self.3 = Some(address.into()); self } async fn request(&self, method: &str, params: T) -> Result where T: Debug + Serialize + Send + Sync, R: Serialize + DeserializeOwned + Debug, { let span = tracing::trace_span!("rpc", method = method, params = ?serde_json::to_string(¶ms)?); // https://docs.rs/tracing/0.1.22/tracing/span/struct.Span.html#in-asynchronous-code let res = async move { trace!("tx"); let res: R = self.0.request(method, params).await.map_err(Into::into)?; trace!(rx = ?serde_json::to_string(&res)?); Ok::<_, ProviderError>(res) } .instrument(span) .await?; Ok(res) } async fn get_block_gen( &self, id: BlockId, include_txs: bool, ) -> Result>, ProviderError> { let include_txs = utils::serialize(&include_txs); Ok(match id { BlockId::Hash(hash) => { let hash = utils::serialize(&hash); self.request("eth_getBlockByHash", [hash, include_txs]) .await? } BlockId::Number(num) => { let num = utils::serialize(&num); self.request("eth_getBlockByNumber", [num, include_txs]) .await? } }) } } #[cfg(feature = "celo")] #[async_trait] impl CeloMiddleware for Provider

{ async fn get_validators_bls_public_keys + Send + Sync>( &self, block_id: T, ) -> Result, ProviderError> { let block_id = utils::serialize(&block_id.into()); self.request("istanbul_getValidatorsBLSPublicKeys", [block_id]) .await } } #[async_trait] impl Middleware for Provider

{ type Error = ProviderError; type Provider = P; type Inner = Self; fn inner(&self) -> &Self::Inner { unreachable!("There is no inner provider here") } fn provider(&self) -> &Provider { self } ////// Blockchain Status // // Functions for querying the state of the blockchain /// Gets the latest block number via the `eth_BlockNumber` API async fn get_block_number(&self) -> Result { self.request("eth_blockNumber", ()).await } /// Gets the block at `block_hash_or_number` (transaction hashes only) async fn get_block + Send + Sync>( &self, block_hash_or_number: T, ) -> Result>, Self::Error> { self.get_block_gen(block_hash_or_number.into(), false).await } /// Gets the block at `block_hash_or_number` (full transactions included) async fn get_block_with_txs + Send + Sync>( &self, block_hash_or_number: T, ) -> Result>, ProviderError> { self.get_block_gen(block_hash_or_number.into(), true).await } /// Gets the transaction with `transaction_hash` async fn get_transaction>( &self, transaction_hash: T, ) -> Result, ProviderError> { let hash = transaction_hash.into(); self.request("eth_getTransactionByHash", [hash]).await } /// Gets the transaction receipt with `transaction_hash` async fn get_transaction_receipt>( &self, transaction_hash: T, ) -> Result, ProviderError> { let hash = transaction_hash.into(); self.request("eth_getTransactionReceipt", [hash]).await } /// Gets the current gas price as estimated by the node async fn get_gas_price(&self) -> Result { self.request("eth_gasPrice", ()).await } /// Gets the accounts on the node async fn get_accounts(&self) -> Result, ProviderError> { self.request("eth_accounts", ()).await } /// Returns the nonce of the address async fn get_transaction_count + Send + Sync>( &self, from: T, block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, NameOrAddress::Address(addr) => addr, }; let from = utils::serialize(&from); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getTransactionCount", [from, block]).await } /// Returns the account's balance async fn get_balance + Send + Sync>( &self, from: T, block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, NameOrAddress::Address(addr) => addr, }; let from = utils::serialize(&from); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getBalance", [from, block]).await } /// Returns the currently configured chain id, a value used in replay-protected /// transaction signing as introduced by EIP-155. async fn get_chainid(&self) -> Result { self.request("eth_chainId", ()).await } ////// Contract Execution // // These are relatively low-level calls. The Contracts API should usually be used instead. /// Sends the read-only (constant) transaction to a single Ethereum node and return the result (as bytes) of executing it. /// This is free, since it does not change any state on the blockchain. async fn call( &self, tx: &TransactionRequest, block: Option, ) -> Result { let tx = utils::serialize(tx); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_call", [tx, block]).await } /// Sends a transaction to a single Ethereum node and return the estimated amount of gas required (as a U256) to send it /// This is free, but only an estimate. Providing too little gas will result in a transaction being rejected /// (while still consuming all provided gas). async fn estimate_gas(&self, tx: &TransactionRequest) -> Result { self.request("eth_estimateGas", [tx]).await } /// Sends the transaction to the entire Ethereum network and returns the transaction's hash /// This will consume gas from the account that signed the transaction. async fn send_transaction( &self, mut tx: TransactionRequest, _: Option, ) -> Result, ProviderError> { if tx.from.is_none() { tx.from = self.3; } if tx.gas.is_none() { tx.gas = Some(self.estimate_gas(&tx).await?); } if let Some(NameOrAddress::Name(ref ens_name)) = tx.to { // resolve to an address let addr = self.resolve_name(&ens_name).await?; // set the value tx.to = Some(addr.into()) } let tx_hash = self.request("eth_sendTransaction", [tx]).await?; Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) } /// 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. async fn send_raw_transaction<'a>( &'a self, tx: &Transaction, ) -> Result, ProviderError> { let rlp = utils::serialize(&tx.rlp()); let tx_hash = self.request("eth_sendRawTransaction", [rlp]).await?; Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) } /// The JSON-RPC provider is at the bottom-most position in the middleware stack. Here we check /// if it has the key for the sender address unlocked, as well as supports the `eth_sign` call. async fn is_signer(&self) -> bool { match self.3 { Some(sender) => self.sign(vec![], &sender).await.is_ok(), None => false, } } /// Signs data using a specific account. This account needs to be unlocked. async fn sign + Send + Sync>( &self, data: T, from: &Address, ) -> Result { let data = utils::serialize(&data.into()); let from = utils::serialize(from); // get the response from `eth_sign` call and trim the 0x-prefix if present. let sig: String = self.request("eth_sign", [from, data]).await?; let sig = sig.strip_prefix("0x").unwrap_or(&sig); // decode the signature. let sig = hex::decode(sig)?; Ok(Signature::try_from(sig.as_slice()) .map_err(|e| ProviderError::CustomError(e.to_string()))?) } ////// Contract state /// Returns an array (possibly empty) of logs that match the filter async fn get_logs(&self, filter: &Filter) -> Result, ProviderError> { self.request("eth_getLogs", [filter]).await } /// Streams matching filter logs async fn watch<'a>( &'a self, filter: &Filter, ) -> Result, ProviderError> { let id = self.new_filter(FilterKind::Logs(filter)).await?; let filter = FilterWatcher::new(id, self).interval(self.get_interval()); Ok(filter) } /// Streams new block hashes async fn watch_blocks(&self) -> Result, ProviderError> { let id = self.new_filter(FilterKind::NewBlocks).await?; let filter = FilterWatcher::new(id, self).interval(self.get_interval()); Ok(filter) } /// Streams pending transactions async fn watch_pending_transactions( &self, ) -> Result, ProviderError> { let id = self.new_filter(FilterKind::PendingTransactions).await?; let filter = FilterWatcher::new(id, self).interval(self.get_interval()); Ok(filter) } /// Creates a filter object, based on filter options, to notify when the state changes (logs). /// To check if the state has changed, call `get_filter_changes` with the filter id. async fn new_filter(&self, filter: FilterKind<'_>) -> Result { let (method, args) = match filter { FilterKind::NewBlocks => ("eth_newBlockFilter", vec![]), FilterKind::PendingTransactions => ("eth_newPendingTransactionFilter", vec![]), FilterKind::Logs(filter) => ("eth_newFilter", vec![utils::serialize(&filter)]), }; self.request(method, args).await } /// Uninstalls a filter async fn uninstall_filter + Send + Sync>( &self, id: T, ) -> Result { let id = utils::serialize(&id.into()); self.request("eth_uninstallFilter", [id]).await } /// Polling method for a filter, which returns an array of logs which occurred since last poll. /// /// This method must be called with one of the following return types, depending on the filter /// type: /// - `eth_newBlockFilter`: [`H256`], returns block hashes /// - `eth_newPendingTransactionFilter`: [`H256`], returns transaction hashes /// - `eth_newFilter`: [`Log`], returns raw logs /// /// If one of these types is not used, decoding will fail and the method will /// return an error. /// /// [`H256`]: ethers_core::types::H256 /// [`Log`]: ethers_core::types::Log async fn get_filter_changes(&self, id: T) -> Result, ProviderError> where T: Into + Send + Sync, R: Serialize + DeserializeOwned + Send + Sync + Debug, { let id = utils::serialize(&id.into()); self.request("eth_getFilterChanges", [id]).await } /// Get the storage of an address for a particular slot location async fn get_storage_at + Send + Sync>( &self, from: T, location: H256, block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, NameOrAddress::Address(addr) => addr, }; let from = utils::serialize(&from); let location = utils::serialize(&location); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); // get the hex encoded value. let value: String = self .request("eth_getStorageAt", [from, location, block]) .await?; // get rid of the 0x prefix and left pad it with zeroes. let value = format!("{:0>64}", value.replace("0x", "")); Ok(H256::from_slice(&Vec::from_hex(value)?)) } /// Returns the deployed code at a given address async fn get_code + Send + Sync>( &self, at: T, block: Option, ) -> Result { let at = match at.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, NameOrAddress::Address(addr) => addr, }; let at = utils::serialize(&at); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getCode", [at, block]).await } ////// Ethereum Naming Service // The Ethereum Naming Service (ENS) allows easy to remember and use names to // be assigned to Ethereum addresses. Any provider operation which takes an address // may also take an ENS name. // // ENS also provides the ability for a reverse lookup, which determines the name for an address if it has been configured. /// Returns the address that the `ens_name` resolves to (or None if not configured). /// /// # Panics /// /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as /// an address. This should theoretically never happen. async fn resolve_name(&self, ens_name: &str) -> Result { self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR) .await } /// Returns the ENS name the `address` resolves to (or None if not configured). /// # Panics /// /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as /// a string. This should theoretically never happen. async fn lookup_address(&self, address: Address) -> Result { let ens_name = ens::reverse_address(address); self.query_resolver(ParamType::String, &ens_name, ens::NAME_SELECTOR) .await } /// Returns the details of all transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) async fn txpool_content(&self) -> Result { self.request("txpool_content", ()).await } /// Returns a summary of all the transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) async fn txpool_inspect(&self) -> Result { self.request("txpool_inspect", ()).await } /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) async fn txpool_status(&self) -> Result { self.request("txpool_status", ()).await } /// Executes the given call and returns a number of possible traces for it async fn trace_call( &self, req: TransactionRequest, trace_type: Vec, block: Option, ) -> Result { let req = utils::serialize(&req); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); let trace_type = utils::serialize(&trace_type); self.request("trace_call", [req, trace_type, block]).await } /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces async fn trace_raw_transaction( &self, data: Bytes, trace_type: Vec, ) -> Result { let data = utils::serialize(&data); let trace_type = utils::serialize(&trace_type); self.request("trace_rawTransaction", [data, trace_type]) .await } /// Replays a transaction, returning the traces async fn trace_replay_transaction( &self, hash: H256, trace_type: Vec, ) -> Result { let hash = utils::serialize(&hash); let trace_type = utils::serialize(&trace_type); self.request("trace_replayTransaction", [hash, trace_type]) .await } /// Replays all transactions in a block returning the requested traces for each transaction async fn trace_replay_block_transactions( &self, block: BlockNumber, trace_type: Vec, ) -> Result, ProviderError> { let block = utils::serialize(&block); let trace_type = utils::serialize(&trace_type); self.request("trace_replayBlockTransactions", [block, trace_type]) .await } /// Returns traces created at given block async fn trace_block(&self, block: BlockNumber) -> Result, ProviderError> { let block = utils::serialize(&block); self.request("trace_block", [block]).await } /// Return traces matching the given filter async fn trace_filter(&self, filter: TraceFilter) -> Result, ProviderError> { let filter = utils::serialize(&filter); self.request("trace_filter", vec![filter]).await } /// Returns trace at the given position async fn trace_get + Send + Sync>( &self, hash: H256, index: Vec, ) -> Result { let hash = utils::serialize(&hash); let index: Vec = index.into_iter().map(|i| i.into()).collect(); let index = utils::serialize(&index); self.request("trace_get", vec![hash, index]).await } /// Returns all traces of a given transaction async fn trace_transaction(&self, hash: H256) -> Result, ProviderError> { let hash = utils::serialize(&hash); self.request("trace_transaction", vec![hash]).await } /// Returns all receipts for that block. Must be done on a parity node. async fn parity_block_receipts + Send + Sync>( &self, block: T, ) -> Result, Self::Error> { self.request("parity_getBlockReceipts", vec![block.into()]) .await } async fn subscribe( &self, params: T, ) -> Result, ProviderError> where T: Debug + Serialize + Send + Sync, R: DeserializeOwned + Send + Sync, P: PubsubClient, { let id: U256 = self.request("eth_subscribe", params).await?; SubscriptionStream::new(id, self).map_err(Into::into) } async fn unsubscribe(&self, id: T) -> Result where T: Into + Send + Sync, P: PubsubClient, { self.request("eth_unsubscribe", [id.into()]).await } async fn subscribe_blocks( &self, ) -> Result>, ProviderError> where P: PubsubClient, { self.subscribe(["newHeads"]).await } async fn subscribe_pending_txs( &self, ) -> Result, ProviderError> where P: PubsubClient, { self.subscribe(["newPendingTransactions"]).await } async fn subscribe_logs<'a>( &'a self, filter: &Filter, ) -> Result, ProviderError> where P: PubsubClient, { let logs = utils::serialize(&"logs"); // TODO: Make this a static let filter = utils::serialize(filter); self.subscribe([logs, filter]).await } } impl Provider

{ async fn query_resolver( &self, param: ParamType, ens_name: &str, selector: Selector, ) -> Result { // Get the ENS address, prioritize the local override variable let ens_addr = self.1.unwrap_or(ens::ENS_ADDRESS); // first get the resolver responsible for this name // the call will return a Bytes array which we convert to an address let data = self .call(&ens::get_resolver(ens_addr, ens_name), None) .await?; let resolver_address: Address = decode_bytes(ParamType::Address, data); if resolver_address == Address::zero() { return Err(ProviderError::EnsError(ens_name.to_owned())); } // resolve let data = self .call(&ens::resolve(resolver_address, selector, ens_name), None) .await?; Ok(decode_bytes(param, data)) } #[cfg(test)] /// ganache-only function for mining empty blocks pub async fn mine(&self, num_blocks: usize) -> Result<(), ProviderError> { for _ in 0..num_blocks { self.0 .request::<_, U256>("evm_mine", None::<()>) .await .map_err(Into::into)?; } Ok(()) } /// Sets the ENS Address (default: mainnet) pub fn ens>(mut self, ens: T) -> Self { self.1 = Some(ens.into()); self } /// Sets the default polling interval for event filters and pending transactions /// (default: 7 seconds) pub fn interval>(mut self, interval: T) -> Self { self.2 = Some(interval.into()); self } /// Gets the polling interval which the provider currently uses for event filters /// and pending transactions (default: 7 seconds) pub fn get_interval(&self) -> Duration { self.2.unwrap_or(DEFAULT_POLL_INTERVAL) } } #[cfg(feature = "ws")] impl Provider { /// Direct connection to a websocket endpoint pub async fn connect( url: impl tokio_tungstenite::tungstenite::client::IntoClientRequest + Unpin, ) -> Result { let ws = crate::Ws::connect(url).await?; Ok(Self::new(ws)) } } #[cfg(feature = "ipc")] impl Provider { /// Direct connection to an IPC socket. pub async fn connect_ipc(path: impl AsRef) -> Result { let ipc = crate::Ipc::connect(path).await?; Ok(Self::new(ipc)) } } impl Provider { /// Returns a `Provider` instantiated with an internal "mock" transport. /// /// # Example /// /// ``` /// # async fn foo() -> Result<(), Box> { /// use ethers::{types::U64, providers::{Middleware, Provider}}; /// // Instantiate the provider /// let (provider, mock) = Provider::mocked(); /// // Push the mock response /// mock.push(U64::from(12))?; /// // Make the call /// let blk = provider.get_block_number().await.unwrap(); /// // The response matches /// assert_eq!(blk.as_u64(), 12); /// // and the request as well! /// mock.assert_request("eth_blockNumber", ()).unwrap(); /// # Ok(()) /// # } /// ``` pub fn mocked() -> (Self, MockProvider) { let mock = MockProvider::new(); let mock_clone = mock.clone(); (Self::new(mock), mock_clone) } } /// infallible conversion of Bytes to Address/String /// /// # Panics /// /// If the provided bytes were not an interpretation of an address fn decode_bytes(param: ParamType, bytes: Bytes) -> T { let tokens = abi::decode(&[param], &bytes.as_ref()) .expect("could not abi-decode bytes to address tokens"); T::from_tokens(tokens).expect("could not parse tokens as address") } impl TryFrom<&str> for Provider { type Error = ParseError; fn try_from(src: &str) -> Result { Ok(Provider( HttpProvider::new(Url::parse(src)?), None, None, None, )) } } impl TryFrom for Provider { type Error = ParseError; fn try_from(src: String) -> Result { Provider::try_from(src.as_str()) } } #[cfg(test)] mod ens_tests { use super::*; const INFURA: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27"; #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_resolve_name() { let provider = Provider::::try_from(INFURA).unwrap(); let addr = provider .resolve_name("registrar.firefly.eth") .await .unwrap(); assert_eq!( addr, "6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap() ); // registrar not found provider.resolve_name("asdfasdffads").await.unwrap_err(); // name not found provider .resolve_name("asdfasdf.registrar.firefly.eth") .await .unwrap_err(); } #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_lookup_address() { let provider = Provider::::try_from(INFURA).unwrap(); let name = provider .lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()) .await .unwrap(); assert_eq!(name, "registrar.firefly.eth"); provider .lookup_address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse().unwrap()) .await .unwrap_err(); } } #[cfg(test)] mod tests { use super::*; use crate::Http; use ethers_core::types::H256; use ethers_core::utils::Geth; use futures_util::StreamExt; #[tokio::test] #[cfg_attr(feature = "celo", ignore)] async fn test_new_block_filter() { let num_blocks = 3; let geth = Geth::new().block_time(2u64).spawn(); let provider = Provider::::try_from(geth.endpoint()) .unwrap() .interval(Duration::from_millis(1000)); let start_block = provider.get_block_number().await.unwrap(); let stream = provider.watch_blocks().await.unwrap().stream(); let hashes: Vec = stream.take(num_blocks).collect::>().await; for (i, hash) in hashes.iter().enumerate() { let block = provider .get_block(start_block + i as u64 + 1) .await .unwrap() .unwrap(); assert_eq!(*hash, block.hash.unwrap()); } } #[tokio::test] #[cfg_attr(feature = "celo", ignore)] async fn test_is_signer() { use ethers_core::utils::Ganache; use std::str::FromStr; let ganache = Ganache::new().spawn(); let provider = Provider::::try_from(ganache.endpoint()) .unwrap() .with_sender(ganache.addresses()[0]); assert!(provider.is_signer().await); let provider = Provider::::try_from(ganache.endpoint()).unwrap(); assert!(!provider.is_signer().await); let sender = Address::from_str("635B4764D1939DfAcD3a8014726159abC277BecC") .expect("should be able to parse hex address"); let provider = Provider::::try_from( "https://ropsten.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778", ) .unwrap() .with_sender(sender); assert!(!provider.is_signer().await); } #[tokio::test] async fn test_new_pending_txs_filter() { let num_txs = 5; let geth = Geth::new().block_time(2u64).spawn(); let provider = Provider::::try_from(geth.endpoint()) .unwrap() .interval(Duration::from_millis(1000)); let accounts = provider.get_accounts().await.unwrap(); let stream = provider .watch_pending_transactions() .await .unwrap() .stream(); let mut tx_hashes = Vec::new(); let tx = TransactionRequest::new() .from(accounts[0]) .to(accounts[0]) .value(1e18 as u64); for _ in 0..num_txs { tx_hashes.push(provider.send_transaction(tx.clone(), None).await.unwrap()); } let hashes: Vec = stream.take(num_txs).collect::>().await; assert_eq!(tx_hashes, hashes); } #[tokio::test] async fn receipt_on_unmined_tx() { use ethers_core::{ types::TransactionRequest, utils::{parse_ether, Ganache}, }; let ganache = Ganache::new().block_time(2u64).spawn(); let provider = Provider::::try_from(ganache.endpoint()).unwrap(); 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, None).await.unwrap(); assert!(provider .get_transaction_receipt(*pending_tx) .await .unwrap() .is_none()); let hash = *pending_tx; let receipt = pending_tx.await.unwrap(); assert_eq!(receipt.transaction_hash, hash); } #[tokio::test] async fn parity_block_receipts() { let url = match std::env::var("PARITY") { Ok(inner) => inner, _ => return, }; let provider = Provider::::try_from(url.as_str()).unwrap(); let receipts = provider.parity_block_receipts(10657200).await.unwrap(); assert!(!receipts.is_empty()); } #[tokio::test] // Celo blocks can not get parsed when used with Ganache #[cfg(not(feature = "celo"))] async fn block_subscribe() { use ethers_core::utils::Ganache; use futures_util::StreamExt; let ganache = Ganache::new().block_time(2u64).spawn(); let provider = Provider::connect(ganache.ws_endpoint()).await.unwrap(); let stream = provider.subscribe_blocks().await.unwrap(); let blocks = stream .take(3) .map(|x| x.number.unwrap().as_u64()) .collect::>() .await; assert_eq!(blocks, vec![1, 2, 3]); } }