use crate::{ens, http::Provider as HttpProvider, JsonRpcClient}; use ethers_core::{ abi::{self, Detokenize, ParamType}, types::{ Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, NameOrAddress, Selector, Signature, Transaction, TransactionReceipt, TransactionRequest, TxHash, U256, U64, }, utils, }; use serde::Deserialize; use thiserror::Error; use url::{ParseError, Url}; use std::{convert::TryFrom, fmt::Debug}; /// 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 /// (e.g. HTTP, Websockets etc.) #[derive(Clone, Debug)] pub struct Provider

(P, Option

); #[derive(Debug, Error)] /// An error thrown when making a call to the provider pub enum ProviderError { // dyn dispatch the error to avoid generic return types #[error(transparent)] JsonRpcClientError(#[from] Box), #[error("ens name not found: {0}")] EnsError(String), } // JSON RPC bindings impl Provider

{ /// Instantiate a new provider with a backend. pub fn new(provider: P) -> Self { Self(provider, None) } ////// Blockchain Status // // Functions for querying the state of the blockchain /// Gets the latest block number via the `eth_BlockNumber` API pub async fn get_block_number(&self) -> Result { Ok(self .0 .request("eth_blockNumber", None::<()>) .await .map_err(Into::into)?) } /// Gets the block at `block_hash_or_number` (transaction hashes only) pub async fn get_block( &self, block_hash_or_number: impl Into, ) -> Result, ProviderError> { Ok(self .get_block_gen(block_hash_or_number.into(), false) .await?) } /// Gets the block at `block_hash_or_number` (full transactions included) pub async fn get_block_with_txs( &self, block_hash_or_number: impl Into, ) -> Result, ProviderError> { Ok(self .get_block_gen(block_hash_or_number.into(), true) .await?) } async fn get_block_gen Deserialize<'a>>( &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); let args = vec![hash, include_txs]; self.0 .request("eth_getBlockByHash", Some(args)) .await .map_err(Into::into)? } BlockId::Number(num) => { let num = utils::serialize(&num); let args = vec![num, include_txs]; self.0 .request("eth_getBlockByNumber", Some(args)) .await .map_err(Into::into)? } }) } /// Gets the transaction with `transaction_hash` pub async fn get_transaction>( &self, transaction_hash: T, ) -> Result { let hash = transaction_hash.into(); Ok(self .0 .request("eth_getTransactionByHash", Some(hash)) .await .map_err(Into::into)?) } /// Gets the transaction receipt with `transaction_hash` pub async fn get_transaction_receipt>( &self, transaction_hash: T, ) -> Result { let hash = transaction_hash.into(); Ok(self .0 .request("eth_getTransactionReceipt", Some(hash)) .await .map_err(Into::into)?) } /// Gets the current gas price as estimated by the node pub async fn get_gas_price(&self) -> Result { Ok(self .0 .request("eth_gasPrice", None::<()>) .await .map_err(Into::into)?) } /// Gets the accounts on the node pub async fn get_accounts(&self) -> Result, ProviderError> { Ok(self .0 .request("eth_accounts", None::<()>) .await .map_err(Into::into)?) } /// Returns the nonce of the address pub async fn get_transaction_count( &self, from: Address, block: Option, ) -> Result { let from = utils::serialize(&from); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); Ok(self .0 .request("eth_getTransactionCount", Some(&[from, block])) .await .map_err(Into::into)?) } /// Returns the account's balance pub async fn get_balance( &self, from: Address, block: Option, ) -> Result { let from = utils::serialize(&from); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); Ok(self .0 .request("eth_getBalance", Some(&[from, block])) .await .map_err(Into::into)?) } /// Returns the currently configured chain id, a value used in replay-protected /// transaction signing as introduced by EIP-155. pub async fn get_chainid(&self) -> Result { Ok(self .0 .request("eth_chainId", None::<()>) .await .map_err(Into::into)?) } ////// Contract Execution // // These are relatively low-level calls. The Contracts API should usually be used instead. /// Send 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. pub async fn call( &self, tx: &TransactionRequest, block: Option, ) -> Result { let tx = utils::serialize(tx); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); Ok(self .0 .request("eth_call", Some(vec![tx, block])) .await .map_err(Into::into)?) } /// Send 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). pub async fn estimate_gas( &self, tx: &TransactionRequest, block: Option, ) -> Result { let tx = utils::serialize(tx); let args = match block { Some(block) => vec![tx, utils::serialize(&block)], None => vec![tx], }; Ok(self .0 .request("eth_estimateGas", Some(args)) .await .map_err(Into::into)?) } /// Send the transaction to the entire Ethereum network and returns the transaction's hash /// This will consume gas from the account that signed the transaction. pub async fn send_transaction( &self, mut tx: TransactionRequest, ) -> Result { if let Some(ref to) = tx.to { if let NameOrAddress::Name(ens_name) = to { // resolve to an address let addr = self .resolve_name(&ens_name) .await? .ok_or_else(|| ProviderError::EnsError(ens_name.to_owned()))?; // set the value tx.to = Some(addr.into()) } } Ok(self .0 .request("eth_sendTransaction", Some(tx)) .await .map_err(Into::into)?) } /// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash /// This will consume gas from the account that signed the transaction. pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result { let rlp = utils::serialize(&tx.rlp()); Ok(self .0 .request("eth_sendRawTransaction", Some(rlp)) .await .map_err(Into::into)?) } /// Signs data using a specific account. This account needs to be unlocked. pub async fn sign(&self, data: &Bytes, from: &Address) -> Result { let data = utils::serialize(data); let from = utils::serialize(from); Ok(self .0 .request("eth_sign", Some(vec![from, data])) .await .map_err(Into::into)?) } ////// Contract state /// Returns an array (possibly empty) of logs that match the filter pub async fn get_logs(&self, filter: &Filter) -> Result, ProviderError> { Ok(self .0 .request("eth_getLogs", Some(filter)) .await .map_err(Into::into)?) } // TODO: get_code, get_storage_at ////// 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. pub async fn resolve_name(&self, ens_name: &str) -> Result, ProviderError> { 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. pub async fn lookup_address(&self, address: Address) -> Result, ProviderError> { let ens_name = ens::reverse_address(address); self.query_resolver(ParamType::String, &ens_name, ens::NAME_SELECTOR) .await } async fn query_resolver( &self, param: ParamType, ens_name: &str, selector: Selector, ) -> Result, ProviderError> { // 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 Ok(None); } // resolve let data = self .call(&ens::resolve(resolver_address, selector, ens_name), None) .await?; Ok(Some(decode_bytes(param, data))) } /// Overrides the default ENS address set by the provider's `Network` type. pub fn ens>(mut self, ens: T) -> Self { self.1 = Some(ens.into()); self } } /// infallbile 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.0).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)) } } #[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.unwrap(), "6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap() ); // registrar not found let addr = provider.resolve_name("asdfasdffads").await.unwrap(); assert!(addr.is_none()); // name not found let addr = provider .resolve_name("asdfasdf.registrar.firefly.eth") .await .unwrap(); assert!(addr.is_none()); } #[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.unwrap(), "registrar.firefly.eth"); let name = provider .lookup_address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse().unwrap()) .await .unwrap(); assert!(name.is_none()); } }