use crate::{ ens, erc, maybe, pubsub::{PubsubClient, SubscriptionStream}, stream::{FilterWatcher, DEFAULT_POLL_INTERVAL}, FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, MockProvider, PendingTransaction, QuorumProvider, SyncingStatus, }; #[cfg(feature = "celo")] use crate::CeloMiddleware; use crate::Middleware; use async_trait::async_trait; use ethers_core::{ abi::{self, Detokenize, ParamType}, types::{ transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}, Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, EIP1186ProofResponse, FeeHistory, Filter, Log, NameOrAddress, Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, }, utils, }; use hex::FromHex; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; use url::{ParseError, Url}; use futures_util::{lock::Mutex, try_join}; use std::{convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration}; use tracing::trace; use tracing_futures::Instrument; #[derive(Copy, Clone)] pub enum NodeClient { Geth, Erigon, OpenEthereum, Nethermind, Besu, } impl FromStr for NodeClient { type Err = ProviderError; fn from_str(s: &str) -> Result { match s.split('/').next().unwrap().to_lowercase().as_str() { "geth" => Ok(NodeClient::Geth), "erigon" => Ok(NodeClient::Erigon), "openethereum" => Ok(NodeClient::OpenEthereum), "nethermind" => Ok(NodeClient::Nethermind), "besu" => Ok(NodeClient::Besu), _ => Err(ProviderError::UnsupportedNodeClient), } } } /// 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)] pub struct Provider

{ inner: P, ens: Option

, interval: Option, from: Option
, /// Node client hasn't been checked yet = `None` /// Unsupported node client = `Some(None)` /// Supported node client = `Some(Some(NodeClient))` _node_client: Arc>>, } impl

AsRef

for Provider

{ fn as_ref(&self) -> &P { &self.inner } } 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(transparent)] HTTPError(#[from] reqwest::Error), #[error("custom error: {0}")] CustomError(String), #[error("unsupported RPC")] UnsupportedRPC, #[error("unsupported node client")] UnsupportedNodeClient, #[error("Attempted to sign a transaction with no available signer. Hint: did you mean to use a SignerMiddleware?")] SignerUnavailable, } /// 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 { inner: provider, ens: None, interval: None, from: None, _node_client: Arc::new(Mutex::new(None)), } } /// Returns the type of node we're connected to, while also caching the value for use /// in other node-specific API calls, such as the get_block_receipts call. pub async fn node_client(&self) -> Result { let mut node_client = self._node_client.lock().await; if let Some(node_client) = *node_client { Ok(node_client) } else { let client_version = self.client_version().await?; let client_version = match client_version.parse::() { Ok(res) => res, Err(_) => return Err(ProviderError::UnsupportedNodeClient), }; *node_client = Some(client_version); Ok(client_version) } } #[must_use] pub fn with_sender(mut self, address: impl Into

) -> Self { self.from = Some(address.into()); self } pub 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.inner.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")] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), 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 } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), 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 } fn default_sender(&self) -> Option

{ self.from } ////// Blockchain Status // // Functions for querying the state of the blockchain /// Returns the current client version using the `web3_clientVersion` RPC. async fn client_version(&self) -> Result { self.request("web3_clientVersion", ()).await } async fn fill_transaction( &self, tx: &mut TypedTransaction, block: Option, ) -> Result<(), Self::Error> { if let Some(default_sender) = self.default_sender() { if tx.from().is_none() { tx.set_from(default_sender); } } // TODO: Join the name resolution and gas price future // set the ENS name if let Some(NameOrAddress::Name(ref ens_name)) = tx.to() { let addr = self.resolve_name(ens_name).await?; tx.set_to(addr); } // fill gas price match tx { TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => { let gas_price = maybe(tx.gas_price(), self.get_gas_price()).await?; tx.set_gas_price(gas_price); } TypedTransaction::Eip1559(ref mut inner) => { if inner.max_fee_per_gas.is_none() || inner.max_priority_fee_per_gas.is_none() { let (max_fee_per_gas, max_priority_fee_per_gas) = self.estimate_eip1559_fees(None).await?; inner.max_fee_per_gas = Some(max_fee_per_gas); inner.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); }; } } // If the tx has an access list but it is empty, it is an Eip1559 or Eip2930 tx, // and we attempt to populate the acccess list. This may require `eth_estimateGas`, // in which case we save the result in maybe_gas_res for later let mut maybe_gas = None; if let Some(starting_al) = tx.access_list() { if starting_al.0.is_empty() { let (gas_res, al_res) = futures_util::join!( maybe(tx.gas().cloned(), self.estimate_gas(tx)), self.create_access_list(tx, block) ); let mut gas = gas_res?; if let Ok(al_with_gas) = al_res { // Set access list if it saves gas over the estimated (or previously set) value if al_with_gas.gas_used < gas { // Update the gas estimate with the lower amount gas = al_with_gas.gas_used; tx.set_access_list(al_with_gas.access_list); } } maybe_gas = Some(gas); } } // Set gas to estimated value only if it was not set by the caller, // even if the access list has been populated and saves gas if tx.gas().is_none() { let gas_estimate = maybe(maybe_gas, self.estimate_gas(tx)).await?; tx.set_gas(gas_estimate); } Ok(()) } /// 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 block uncle count at `block_hash_or_number` async fn get_uncle_count + Send + Sync>( &self, block_hash_or_number: T, ) -> Result { let id = block_hash_or_number.into(); Ok(match id { BlockId::Hash(hash) => { let hash = utils::serialize(&hash); self.request("eth_getUncleCountByBlockHash", [hash]).await? } BlockId::Number(num) => { let num = utils::serialize(&num); self.request("eth_getUncleCountByBlockNumber", [num]).await? } }) } /// Gets the block uncle at `block_hash_or_number` and `idx` async fn get_uncle + Send + Sync>( &self, block_hash_or_number: T, idx: U64, ) -> Result>, ProviderError> { let blk_id = block_hash_or_number.into(); let idx = utils::serialize(&idx); Ok(match blk_id { BlockId::Hash(hash) => { let hash = utils::serialize(&hash); self.request("eth_getUncleByBlockHashAndIndex", [hash, idx]).await? } BlockId::Number(num) => { let num = utils::serialize(&num); self.request("eth_getUncleByBlockNumberAndIndex", [num, idx]).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 } /// Returns all receipts for a block. /// /// Note that this uses the `eth_getBlockReceipts` RPC, which is /// non-standard and currently supported by Erigon. async fn get_block_receipts + Send + Sync>( &self, block: T, ) -> Result, Self::Error> { self.request("eth_getBlockReceipts", [block.into()]).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 } /// Gets the current gas price as estimated by the node async fn get_gas_price(&self) -> Result { self.request("eth_gasPrice", ()).await } /// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for /// EIP-1559 compatible transactions. async fn estimate_eip1559_fees( &self, estimator: Option>) -> (U256, U256)>, ) -> Result<(U256, U256), Self::Error> { let base_fee_per_gas = self .get_block(BlockNumber::Latest) .await? .ok_or_else(|| ProviderError::CustomError("Latest block not found".into()))? .base_fee_per_gas .ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".into()))?; let fee_history = self .fee_history( utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS, BlockNumber::Latest, &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], ) .await?; // use the provided fee estimator function, or fallback to the default implementation. let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator { es(base_fee_per_gas, fee_history.reward) } else { utils::eip1559_default_estimator(base_fee_per_gas, fee_history.reward) }; Ok((max_fee_per_gas, max_priority_fee_per_gas)) } /// 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 } /// Return current client syncing status. If IsFalse sync is over. async fn syncing(&self) -> Result { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum SyncingStatusIntermediate { /// When client is synced to highest block, eth_syncing with return string "false" IsFalse(bool), /// When client is still syncing past blocks we get IsSyncing information. IsSyncing { starting_block: U256, current_block: U256, highest_block: U256 }, } let intermediate: SyncingStatusIntermediate = self.request("eth_syncing", ()).await?; match intermediate { SyncingStatusIntermediate::IsFalse(false) => Ok(SyncingStatus::IsFalse), SyncingStatusIntermediate::IsFalse(true) => Err(ProviderError::CustomError( "eth_syncing returned `true` that is undefined value.".to_owned(), )), SyncingStatusIntermediate::IsSyncing { starting_block, current_block, highest_block, } => Ok(SyncingStatus::IsSyncing { starting_block, current_block, highest_block }), } } /// Returns the network version. async fn get_net_version(&self) -> Result { self.request("net_version", ()).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: &TypedTransaction, 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: &TypedTransaction) -> Result { self.request("eth_estimateGas", [tx]).await } async fn create_access_list( &self, tx: &TypedTransaction, block: Option, ) -> Result { let tx = utils::serialize(tx); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_createAccessList", [tx, block]).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 + Send + Sync>( &self, tx: T, block: Option, ) -> Result, ProviderError> { let mut tx = tx.into(); self.fill_transaction(&mut tx, block).await?; 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: Bytes, ) -> Result, ProviderError> { let rlp = utils::serialize(&tx); 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.from { 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()))?) } /// Sign a transaction via RPC call async fn sign_transaction( &self, _tx: &TypedTransaction, _from: Address, ) -> Result { Err(ProviderError::SignerUnavailable).map_err(FromErr::from) } ////// 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 } /// Returns the EIP-1186 proof response /// https://github.com/ethereum/EIPs/issues/1186 async fn get_proof + Send + Sync>( &self, from: T, locations: Vec, 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 locations = locations.iter().map(|location| utils::serialize(&location)).collect(); let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getProof", [from, locations, 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 avatar HTTP link of the avatar that the `ens_name` resolves to (or None /// if not configured) /// /// # Example /// ```no_run /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware}; /// # use std::convert::TryFrom; /// # #[tokio::main(flavor = "current_thread")] /// # async fn main() { /// # let provider = Provider::::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap(); /// let avatar = provider.resolve_avatar("parishilton.eth").await.unwrap(); /// assert_eq!(avatar.to_string(), "https://i.imgur.com/YW3Hzph.jpg"); /// # } /// ``` /// /// # Panics /// /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as /// a string. This should theoretically never happen. async fn resolve_avatar(&self, ens_name: &str) -> Result { let (field, owner) = try_join!(self.resolve_field(ens_name, "avatar"), self.resolve_name(ens_name))?; let url = Url::from_str(&field).map_err(|e| ProviderError::CustomError(e.to_string()))?; match url.scheme() { "https" | "data" => Ok(url), "ipfs" => erc::http_link_ipfs(url).map_err(ProviderError::CustomError), "eip155" => { let token = erc::ERCNFT::from_str(url.path()).map_err(ProviderError::CustomError)?; match token.type_ { erc::ERCNFTType::ERC721 => { let tx = TransactionRequest { data: Some( [&erc::ERC721_OWNER_SELECTOR[..], &token.id].concat().into(), ), to: Some(NameOrAddress::Address(token.contract)), ..Default::default() }; let data = self.call(&tx.into(), None).await?; if decode_bytes::
(ParamType::Address, data) != owner { return Err(ProviderError::CustomError("Incorrect owner.".to_string())) } } erc::ERCNFTType::ERC1155 => { let tx = TransactionRequest { data: Some( [ &erc::ERC1155_BALANCE_SELECTOR[..], &[0x0; 12], &owner.0, &token.id, ] .concat() .into(), ), to: Some(NameOrAddress::Address(token.contract)), ..Default::default() }; let data = self.call(&tx.into(), None).await?; if decode_bytes::(ParamType::Uint(64), data) == 0 { return Err(ProviderError::CustomError("Incorrect balance.".to_string())) } } } let image_url = self.resolve_nft(token).await?; match image_url.scheme() { "https" | "data" => Ok(image_url), "ipfs" => erc::http_link_ipfs(image_url).map_err(ProviderError::CustomError), _ => Err(ProviderError::CustomError( "Unsupported scheme for the image".to_string(), )), } } _ => Err(ProviderError::CustomError("Unsupported scheme".to_string())), } } /// Returns the URL (not necesserily HTTP) of the image behind a token. /// /// # Example /// ```no_run /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware}; /// # use std::{str::FromStr, convert::TryFrom}; /// # #[tokio::main(flavor = "current_thread")] /// # async fn main() { /// # let provider = Provider::::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap(); /// let token = ethers_providers::erc::ERCNFT::from_str("erc721:0xc92ceddfb8dd984a89fb494c376f9a48b999aafc/9018").unwrap(); /// let token_image = provider.resolve_nft(token).await.unwrap(); /// assert_eq!(token_image.to_string(), "https://creature.mypinata.cloud/ipfs/QmNwj3aUzXfG4twV3no7hJRYxLLAWNPk6RrfQaqJ6nVJFa/9018.jpg"); /// # } /// ``` /// /// # Panics /// /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as /// a string. This should theoretically never happen. async fn resolve_nft(&self, token: erc::ERCNFT) -> Result { let selector = token.type_.resolution_selector(); let tx = TransactionRequest { data: Some([&selector[..], &token.id].concat().into()), to: Some(NameOrAddress::Address(token.contract)), ..Default::default() }; let data = self.call(&tx.into(), None).await?; let mut metadata_url = Url::parse(&decode_bytes::(ParamType::String, data)) .map_err(|e| ProviderError::CustomError(format!("Invalid metadata url: {}", e)))?; if token.type_ == erc::ERCNFTType::ERC1155 { metadata_url .set_path(&metadata_url.path().replace("%7Bid%7D", &hex::encode(&token.id))); } if metadata_url.scheme() == "ipfs" { metadata_url = erc::http_link_ipfs(metadata_url).map_err(ProviderError::CustomError)?; } let metadata: erc::Metadata = reqwest::get(metadata_url).await?.json().await?; Url::parse(&metadata.image).map_err(|e| ProviderError::CustomError(e.to_string())) } /// Fetch a field for the `ens_name` (no 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 resolve_field(&self, ens_name: &str, field: &str) -> Result { let field: String = self .query_resolver_parameters( ParamType::String, ens_name, ens::FIELD_SELECTOR, Some(&ens::parameterhash(field)), ) .await?; Ok(field) } /// 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 + Send + Sync>( &self, req: T, trace_type: Vec, block: Option, ) -> Result { let req = req.into(); 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 } /// Executes given calls and returns a number of possible traces for each call async fn trace_call_many + Send + Sync>( &self, req: Vec<(T, Vec)>, block: Option, ) -> Result, ProviderError> { let req: Vec<(TypedTransaction, Vec)> = req.into_iter().map(|(tx, trace_type)| (tx.into(), trace_type)).collect(); let req = utils::serialize(&req); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); self.request("trace_callMany", [req, 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 } 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 } async fn fee_history + Send + Sync>( &self, block_count: T, last_block: BlockNumber, reward_percentiles: &[f64], ) -> Result { let block_count = block_count.into(); let last_block = utils::serialize(&last_block); let reward_percentiles = utils::serialize(&reward_percentiles); // The blockCount param is expected to be an unsigned integer up to geth v1.10.6. // Geth v1.10.7 onwards, this has been updated to a hex encoded form. Failure to // decode the param from client side would fallback to the old API spec. self.request( "eth_feeHistory", [utils::serialize(&block_count), last_block.clone(), reward_percentiles.clone()], ) .await .or(self .request( "eth_feeHistory", [utils::serialize(&block_count.as_u64()), last_block, reward_percentiles], ) .await) } } impl Provider

{ async fn query_resolver( &self, param: ParamType, ens_name: &str, selector: Selector, ) -> Result { self.query_resolver_parameters(param, ens_name, selector, None).await } async fn query_resolver_parameters( &self, param: ParamType, ens_name: &str, selector: Selector, parameters: Option<&[u8]>, ) -> Result { // Get the ENS address, prioritize the local override variable let ens_addr = self.ens.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).into(), None).await?; // otherwise, decode_bytes panics if data.0.is_empty() { return Err(ProviderError::EnsError(ens_name.to_owned())) } 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, parameters).into(), 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.inner.request::<_, U256>("evm_mine", None::<()>).await.map_err(Into::into)?; } Ok(()) } /// Sets the ENS Address (default: mainnet) #[must_use] pub fn ens>(mut self, ens: T) -> Self { self.ens = Some(ens.into()); self } /// Sets the default polling interval for event filters and pending transactions /// (default: 7 seconds) #[must_use] pub fn interval>(mut self, interval: T) -> Self { self.interval = 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.interval.unwrap_or(DEFAULT_POLL_INTERVAL) } } #[cfg(feature = "ws")] impl Provider { /// Direct connection to a websocket endpoint #[cfg(not(target_arch = "wasm32"))] pub async fn connect( url: impl tokio_tungstenite::tungstenite::client::IntoClientRequest + Unpin, ) -> Result { let ws = crate::Ws::connect(url).await?; Ok(Self::new(ws)) } /// Direct connection to a websocket endpoint #[cfg(target_arch = "wasm32")] pub async fn connect(url: &str) -> Result { let ws = crate::Ws::connect(url).await?; Ok(Self::new(ws)) } } #[cfg(all(target_family = "unix", 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> { /// Provider that uses a quorum pub fn quorum(inner: QuorumProvider) -> Self { Self::new(inner) } } impl Provider { /// Returns a `Provider` instantiated with an internal "mock" transport. /// /// # Example /// /// ``` /// # async fn foo() -> Result<(), Box> { /// use ethers_core::types::U64; /// use ethers_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::new(HttpProvider::new(Url::parse(src)?))) } } impl TryFrom for Provider { type Error = ParseError; fn try_from(src: String) -> Result { Provider::try_from(src.as_str()) } } /// A middleware supporting development-specific JSON RPC methods /// /// # Example /// ///``` /// use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware}; /// use ethers_core::types::TransactionRequest; /// use ethers_core::utils::Ganache; /// use std::convert::TryFrom; /// /// # #[tokio::main(flavor = "current_thread")] /// # async fn main() -> Result<(), Box> { /// let ganache = Ganache::new().spawn(); /// let provider = Provider::::try_from(ganache.endpoint()).unwrap(); /// let client = DevRpcMiddleware::new(provider); /// /// // snapshot the initial state /// let block0 = client.get_block_number().await.unwrap(); /// let snap_id = client.snapshot().await.unwrap(); /// /// // send a transaction /// let accounts = client.get_accounts().await?; /// let from = accounts[0]; /// let to = accounts[1]; /// let balance_before = client.get_balance(to, None).await?; /// let tx = TransactionRequest::new().to(to).value(1000).from(from); /// client.send_transaction(tx, None).await?.await?; /// let balance_after = client.get_balance(to, None).await?; /// assert_eq!(balance_after, balance_before + 1000); /// /// // revert to snapshot /// client.revert_to_snapshot(snap_id).await.unwrap(); /// let balance_after_revert = client.get_balance(to, None).await?; /// assert_eq!(balance_after_revert, balance_before); /// # Ok(()) /// # } /// ``` #[cfg(feature = "dev-rpc")] pub mod dev_rpc { use crate::{FromErr, Middleware, ProviderError}; use async_trait::async_trait; use ethers_core::types::U256; use thiserror::Error; use std::fmt::Debug; #[derive(Clone, Debug)] pub struct DevRpcMiddleware(M); #[derive(Error, Debug)] pub enum DevRpcMiddlewareError { #[error("{0}")] MiddlewareError(M::Error), #[error("{0}")] ProviderError(ProviderError), #[error("Could not revert to snapshot")] NoSnapshot, } #[async_trait] impl Middleware for DevRpcMiddleware { type Error = DevRpcMiddlewareError; type Provider = M::Provider; type Inner = M; fn inner(&self) -> &M { &self.0 } } impl FromErr for DevRpcMiddlewareError { fn from(src: M::Error) -> DevRpcMiddlewareError { DevRpcMiddlewareError::MiddlewareError(src) } } impl From for DevRpcMiddlewareError where M: Middleware, { fn from(src: ProviderError) -> Self { Self::ProviderError(src) } } impl DevRpcMiddleware { pub fn new(inner: M) -> Self { Self(inner) } // both ganache and hardhat increment snapshot id even if no state has changed pub async fn snapshot(&self) -> Result> { self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from) } pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError> { let ok = self .provider() .request::<[U256; 1], bool>("evm_revert", [id]) .await .map_err(DevRpcMiddlewareError::ProviderError)?; if ok { Ok(()) } else { Err(DevRpcMiddlewareError::NoSnapshot) } } } #[cfg(test)] // Celo blocks can not get parsed when used with Ganache #[cfg(not(feature = "celo"))] mod tests { use super::*; use crate::{Http, Provider}; use ethers_core::utils::Ganache; use std::convert::TryFrom; #[tokio::test] async fn test_snapshot() { // launch ganache let ganache = Ganache::new().spawn(); let provider = Provider::::try_from(ganache.endpoint()).unwrap(); let client = DevRpcMiddleware::new(provider); // snapshot initial state let block0 = client.get_block_number().await.unwrap(); let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp; let snap_id0 = client.snapshot().await.unwrap(); // mine a new block client.provider().mine(1).await.unwrap(); // snapshot state let block1 = client.get_block_number().await.unwrap(); let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp; let snap_id1 = client.snapshot().await.unwrap(); // mine some blocks client.provider().mine(5).await.unwrap(); // snapshot state let block2 = client.get_block_number().await.unwrap(); let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp; let snap_id2 = client.snapshot().await.unwrap(); // mine some blocks client.provider().mine(5).await.unwrap(); // revert_to_snapshot should reset state to snap id client.revert_to_snapshot(snap_id2).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block2); assert_eq!(time, time2); client.revert_to_snapshot(snap_id1).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block1); assert_eq!(time, time1); // revert_to_snapshot should throw given non-existent or // previously used snapshot let result = client.revert_to_snapshot(snap_id1).await; assert!(result.is_err()); client.revert_to_snapshot(snap_id0).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block0); assert_eq!(time, time0); } } } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { use super::*; use crate::Http; use ethers_core::{ types::{ transaction::eip2930::AccessList, Eip1559TransactionRequest, TransactionRequest, H256, }, utils::Geth, }; use futures_util::StreamExt; 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(); } #[tokio::test] async fn mainnet_resolve_avatar() { let provider = Provider::::try_from(INFURA).unwrap(); for (ens_name, res) in &[ // HTTPS ("alisha.eth", "https://ipfs.io/ipfs/QmeQm91kAdPGnUKsE74WvkqYKUeHvc2oHd2FW11V3TrqkQ"), // ERC-1155 ("nick.eth", "https://lh3.googleusercontent.com/hKHZTZSTmcznonu8I6xcVZio1IF76fq0XmcxnvUykC-FGuVJ75UPdLDlKJsfgVXH9wOSmkyHw0C39VAYtsGyxT7WNybjQ6s3fM3macE"), // HTTPS ("parishilton.eth", "https://i.imgur.com/YW3Hzph.jpg"), // ERC-721 with IPFS link ("ikehaya-nft.eth", "https://ipfs.io/ipfs/QmdKkwCE8uVhgYd7tWBfhtHdQZDnbNukWJ8bvQmR6nZKsk"), // ERC-1155 with IPFS link ("vitalik.eth", "https://ipfs.io/ipfs/QmSP4nq9fnN9dAiCj42ug9Wa79rqmQerZXZch82VqpiH7U/image.gif"), // IPFS ("cdixon.eth", "https://ipfs.io/ipfs/QmYA6ZpEARgHvRHZQdFPynMMX8NtdL2JCadvyuyG2oA88u"), ("0age.eth", "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOmJsYWNrIiB2aWV3Qm94PSIwIDAgNTAwIDUwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNTUiIHk9IjYwIiB3aWR0aD0iMTkwIiBoZWlnaHQ9IjM5MCIgZmlsbD0iIzY5ZmYzNyIvPjwvc3ZnPg==") ] { println!("Resolving: {}", ens_name); assert_eq!(provider.resolve_avatar(ens_name).await.unwrap(), Url::parse(res).unwrap()); } } #[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().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]); } #[tokio::test] #[cfg_attr(feature = "celo", ignore)] async fn fee_history() { let provider = Provider::::try_from( "https://goerli.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778", ) .unwrap(); let history = provider.fee_history(10u64, BlockNumber::Latest, &[10.0, 40.0]).await.unwrap(); dbg!(&history); } #[tokio::test] #[ignore] #[cfg(feature = "ws")] async fn test_trace_call_many() { use ethers_core::types::H160; // TODO: Implement ErigonInstance, so it'd be possible to test this. let provider = Provider::new(crate::Ws::connect("ws://127.0.0.1:8545").await.unwrap()); let traces = provider .trace_call_many( vec![ ( TransactionRequest::new() .from(Address::zero()) .to("0x0000000000000000000000000000000000000001" .parse::() .unwrap()) .value(U256::from(10000000000000000u128)), vec![TraceType::StateDiff], ), ( TransactionRequest::new() .from( "0x0000000000000000000000000000000000000001" .parse::() .unwrap(), ) .to("0x0000000000000000000000000000000000000002" .parse::() .unwrap()) .value(U256::from(10000000000000000u128)), vec![TraceType::StateDiff], ), ], None, ) .await .unwrap(); dbg!(traces); } #[tokio::test] async fn test_fill_transaction_1559() { let (mut provider, mock) = Provider::mocked(); provider.from = Some("0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()); let gas = U256::from(21000_usize); let basefee = U256::from(25_usize); let prio_fee = U256::from(25_usize); let access_list: AccessList = vec![Default::default()].into(); // --- leaves a filled 1559 transaction unchanged, making no requests let from: Address = "0x0000000000000000000000000000000000000001".parse().unwrap(); let to: Address = "0x0000000000000000000000000000000000000002".parse().unwrap(); let mut tx = Eip1559TransactionRequest::new() .from(from) .to(to) .gas(gas) .max_fee_per_gas(basefee) .max_priority_fee_per_gas(prio_fee) .access_list(access_list.clone()) .into(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), Some(&from)); assert_eq!(tx.to(), Some(&to.into())); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.gas_price(), Some(basefee + prio_fee)); assert_eq!(tx.access_list(), Some(&access_list)); // --- fills a 1559 transaction, leaving the existing gas limit unchanged, but including // access list if cheaper let gas_with_al = gas - 1; let mut tx = Eip1559TransactionRequest::new() .gas(gas) .max_fee_per_gas(basefee) .max_priority_fee_per_gas(prio_fee) .into(); mock.push(AccessListWithGasUsed { access_list: access_list.clone(), gas_used: gas_with_al, }) .unwrap(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), provider.from.as_ref()); assert!(tx.to().is_none()); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.access_list(), Some(&access_list)); // --- fills a 1559 transaction, ignoring access list if more expensive let gas_with_al = gas + 1; let mut tx = Eip1559TransactionRequest::new() .max_fee_per_gas(basefee) .max_priority_fee_per_gas(prio_fee) .into(); mock.push(AccessListWithGasUsed { access_list: access_list.clone(), gas_used: gas_with_al, }) .unwrap(); mock.push(gas).unwrap(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), provider.from.as_ref()); assert!(tx.to().is_none()); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.access_list(), Some(&Default::default())); // --- fills a 1559 transaction, using estimated gas if create_access_list() errors let mut tx = Eip1559TransactionRequest::new() .max_fee_per_gas(basefee) .max_priority_fee_per_gas(prio_fee) .into(); // bad mock value causes error response for eth_createAccessList mock.push(b'b').unwrap(); mock.push(gas).unwrap(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), provider.from.as_ref()); assert!(tx.to().is_none()); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.access_list(), Some(&Default::default())); // --- propogates estimate_gas() error let mut tx = Eip1559TransactionRequest::new() .max_fee_per_gas(basefee) .max_priority_fee_per_gas(prio_fee) .into(); // bad mock value causes error response for eth_estimateGas mock.push(b'b').unwrap(); let res = provider.fill_transaction(&mut tx, None).await; assert!(matches!(res, Err(ProviderError::JsonRpcClientError(_)))); } #[tokio::test] async fn test_fill_transaction_legacy() { let (mut provider, mock) = Provider::mocked(); provider.from = Some("0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()); let gas = U256::from(21000_usize); let gas_price = U256::from(50_usize); // --- leaves a filled legacy transaction unchanged, making no requests let from: Address = "0x0000000000000000000000000000000000000001".parse().unwrap(); let to: Address = "0x0000000000000000000000000000000000000002".parse().unwrap(); let mut tx = TransactionRequest::new().from(from).to(to).gas(gas).gas_price(gas_price).into(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), Some(&from)); assert_eq!(tx.to(), Some(&to.into())); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.gas_price(), Some(gas_price)); assert!(tx.access_list().is_none()); // --- fills an empty legacy transaction let mut tx = TransactionRequest::new().into(); mock.push(gas).unwrap(); mock.push(gas_price).unwrap(); provider.fill_transaction(&mut tx, None).await.unwrap(); assert_eq!(tx.from(), provider.from.as_ref()); assert!(tx.to().is_none()); assert_eq!(tx.gas(), Some(&gas)); assert_eq!(tx.gas_price(), Some(gas_price)); assert!(tx.access_list().is_none()); } }