refactor(core, contract, etherscan): move Chain enum, use HashMap::from (#524)

* refactor(contract, etherscan): make use of HashMap::from

* feat(core): Chain enum

* rename unknown chain error

* reorg imports
This commit is contained in:
Alexey Shekhirin 2021-10-24 21:41:50 +03:00 committed by GitHub
parent 5779a3cdaf
commit cba1a85483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 74 deletions

View File

@ -1,6 +1,6 @@
use ethers_core::{ use ethers_core::{
abi::{Detokenize, Function, Token}, abi::{Detokenize, Function, Token},
types::{Address, BlockNumber, Bytes, NameOrAddress, TxHash, U256}, types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, U256},
}; };
use ethers_providers::Middleware; use ethers_providers::Middleware;
@ -17,34 +17,33 @@ use multicall_contract::MulticallContract;
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding /// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
/// Multicall smart contract addresses as values /// Multicall smart contract addresses as values
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| { pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
let mut m = HashMap::new(); fn decode_address(input: &str) -> Address {
Address::from_str(input).expect("Decoding failed")
}
// mainnet [
let addr = (
Address::from_str("eefba1e63905ef1d7acba5a8513c70307c1ce441").expect("Decoding failed"); Chain::Mainnet.into(),
m.insert(U256::from(1u8), addr); decode_address("eefba1e63905ef1d7acba5a8513c70307c1ce441"),
),
// rinkeby (
let addr = Chain::Rinkeby.into(),
Address::from_str("42ad527de7d4e9d9d011ac45b31d8551f8fe9821").expect("Decoding failed"); decode_address("42ad527de7d4e9d9d011ac45b31d8551f8fe9821"),
m.insert(U256::from(4u8), addr); ),
(
// goerli Chain::Goerli.into(),
let addr = decode_address("77dca2c955b15e9de4dbbcf1246b4b85b651e50e"),
Address::from_str("77dca2c955b15e9de4dbbcf1246b4b85b651e50e").expect("Decoding failed"); ),
m.insert(U256::from(5u8), addr); (
Chain::Kovan.into(),
// kovan decode_address("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a"),
let addr = ),
Address::from_str("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a").expect("Decoding failed"); (
m.insert(U256::from(42u8), addr); Chain::XDai.into(),
decode_address("b5b692a88bdfc81ca69dcb1d924f59f0413a602a"),
// xdai ),
let addr = ]
Address::from_str("b5b692a88bdfc81ca69dcb1d924f59f0413a602a").expect("Decoding failed"); .into()
m.insert(U256::from(100u8), addr);
m
}); });
/// A Multicall is an abstraction for sending batched calls/transactions to the Ethereum blockchain. /// A Multicall is an abstraction for sending batched calls/transactions to the Ethereum blockchain.

View File

@ -0,0 +1,38 @@
use std::fmt;
use crate::types::U256;
#[derive(Debug)]
pub enum Chain {
Mainnet,
Ropsten,
Rinkeby,
Goerli,
Kovan,
XDai,
}
impl fmt::Display for Chain {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{:?}", self)
}
}
impl From<Chain> for u32 {
fn from(chain: Chain) -> Self {
match chain {
Chain::Mainnet => 1,
Chain::Ropsten => 3,
Chain::Rinkeby => 4,
Chain::Goerli => 5,
Chain::Kovan => 42,
Chain::XDai => 100,
}
}
}
impl From<Chain> for U256 {
fn from(chain: Chain) -> Self {
u32::from(chain).into()
}
}

View File

@ -48,5 +48,9 @@ pub use txpool::*;
mod trace; mod trace;
pub use trace::*; pub use trace::*;
mod chain;
pub use chain::*;
mod proof; mod proof;
pub use proof::*; pub use proof::*;

View File

@ -206,40 +206,42 @@ impl Client {
&self, &self,
guid: impl AsRef<str>, guid: impl AsRef<str>,
) -> Result<Response<String>> { ) -> Result<Response<String>> {
let mut map = HashMap::new(); let body = self.create_query(
map.insert("guid", guid.as_ref()); "contract",
let body = self.create_query("contract", "checkverifystatus", map); "checkverifystatus",
HashMap::from([("guid", guid.as_ref())]),
);
Ok(self.post_form(&body).await?) Ok(self.post_form(&body).await?)
} }
/// Returns the contract ABI of a verified contract /// Returns the contract ABI of a verified contract
/// ///
/// ```no_run /// ```no_run
/// # use ethers_etherscan::{Chain, Client}; /// # use ethers_etherscan::Client;
/// # use ethers_core::types::Chain;
/// ///
/// # #[tokio::main] /// # #[tokio::main]
/// # async fn main() { /// # async fn main() {
/// let client = Client::new(Chain::Mainnet, "API_KEY"); /// let client = Client::new(Chain::Mainnet, "API_KEY").unwrap();
/// let abi = client /// let abi = client
/// .contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) /// .contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
/// .await.unwrap(); /// .await.unwrap();
/// # } /// # }
/// ``` /// ```
pub async fn contract_abi(&self, address: Address) -> Result<Abi> { pub async fn contract_abi(&self, address: Address) -> Result<Abi> {
let mut map = HashMap::new(); let query = self.create_query("contract", "getabi", HashMap::from([("address", address)]));
map.insert("address", address);
let query = self.create_query("contract", "getabi", map);
let resp: Response<String> = self.get_json(&query).await?; let resp: Response<String> = self.get_json(&query).await?;
Ok(serde_json::from_str(&resp.result)?) Ok(serde_json::from_str(&resp.result)?)
} }
/// Get Contract Source Code for Verified Contract Source Codes /// Get Contract Source Code for Verified Contract Source Codes
/// ```no_run /// ```no_run
/// # use ethers_etherscan::{Chain, Client}; /// # use ethers_etherscan::Client;
/// # use ethers_core::types::Chain;
/// ///
/// # #[tokio::main] /// # #[tokio::main]
/// # async fn main() { /// # async fn main() {
/// let client = Client::new(Chain::Mainnet, "API_KEY"); /// let client = Client::new(Chain::Mainnet, "API_KEY").unwrap();
/// let meta = client /// let meta = client
/// .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) /// .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
/// .await.unwrap(); /// .await.unwrap();
@ -247,9 +249,11 @@ impl Client {
/// # } /// # }
/// ``` /// ```
pub async fn contract_source_code(&self, address: Address) -> Result<ContractMetadata> { pub async fn contract_source_code(&self, address: Address) -> Result<ContractMetadata> {
let mut map = HashMap::new(); let query = self.create_query(
map.insert("address", address); "contract",
let query = self.create_query("contract", "getsourcecode", map); "getsourcecode",
HashMap::from([("address", address)]),
);
let response: Response<Vec<Metadata>> = self.get_json(&query).await?; let response: Response<Vec<Metadata>> = self.get_json(&query).await?;
Ok(ContractMetadata { Ok(ContractMetadata {
items: response.result, items: response.result,
@ -259,8 +263,8 @@ impl Client {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::contract::VerifyContract; use crate::{contract::VerifyContract, Client};
use crate::{Chain, Client}; use ethers_core::types::Chain;
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]

View File

@ -1,7 +1,10 @@
use ethers_core::types::Chain;
use std::env::VarError; use std::env::VarError;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum EtherscanError { pub enum EtherscanError {
#[error("chain {0} not supported")]
ChainNotSupported(Chain),
#[error("contract execution call failed: {0}")] #[error("contract execution call failed: {0}")]
ExecutionFailed(String), ExecutionFailed(String),
#[error("tx receipt failed")] #[error("tx receipt failed")]

View File

@ -5,15 +5,15 @@ mod errors;
mod transaction; mod transaction;
use errors::EtherscanError; use errors::EtherscanError;
use ethers_core::abi::Address; use ethers_core::{abi::Address, types::Chain};
use reqwest::{header, Url}; use reqwest::{header, Url};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{borrow::Cow, fmt}; use std::borrow::Cow;
pub type Result<T> = std::result::Result<T, EtherscanError>; pub type Result<T> = std::result::Result<T, EtherscanError>;
/// The Etherscan.io API client. /// The Etherscan.io API client.
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Client { pub struct Client {
/// Client that executes HTTP requests /// Client that executes HTTP requests
client: reqwest::Client, client: reqwest::Client,
@ -25,47 +25,37 @@ pub struct Client {
etherscan_url: Url, etherscan_url: Url,
} }
#[derive(Debug)]
pub enum Chain {
Mainnet,
Ropsten,
Kovan,
Rinkeby,
Goerli,
}
impl fmt::Display for Chain {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}", format!("{:?}", self).to_lowercase())
}
}
impl Client { impl Client {
/// Create a new client with the correct endpoints based on the chain and provided API key /// Create a new client with the correct endpoints based on the chain and provided API key
pub fn new(chain: Chain, api_key: impl Into<String>) -> Self { pub fn new(chain: Chain, api_key: impl Into<String>) -> Result<Self> {
let (etherscan_api_url, etherscan_url) = match chain { let (etherscan_api_url, etherscan_url) = match chain {
Chain::Mainnet => ( Chain::Mainnet => (
Url::parse("https://api.etherscan.io/api"), Url::parse("https://api.etherscan.io/api"),
Url::parse("https://etherscan.io"), Url::parse("https://etherscan.io"),
), ),
Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => ( Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => {
Url::parse(&format!("https://api-{}.etherscan.io/api", chain)), let chain_name = chain.to_string().to_lowercase();
Url::parse(&format!("https://{}.etherscan.io", chain)),
), (
Url::parse(&format!("https://api-{}.etherscan.io/api", chain_name)),
Url::parse(&format!("https://{}.etherscan.io", chain_name)),
)
}
chain => return Err(EtherscanError::ChainNotSupported(chain)),
}; };
Self { Ok(Self {
client: Default::default(), client: Default::default(),
api_key: api_key.into(), api_key: api_key.into(),
etherscan_api_url: etherscan_api_url.expect("is valid http"), etherscan_api_url: etherscan_api_url.expect("is valid http"),
etherscan_url: etherscan_url.expect("is valid http"), etherscan_url: etherscan_url.expect("is valid http"),
} })
} }
/// Create a new client with the correct endpoints based on the chain and API key /// Create a new client with the correct endpoints based on the chain and API key
/// from ETHERSCAN_API_KEY environment variable /// from ETHERSCAN_API_KEY environment variable
pub fn new_from_env(chain: Chain) -> Result<Self> { pub fn new_from_env(chain: Chain) -> Result<Self> {
Ok(Self::new(chain, std::env::var("ETHERSCAN_API_KEY")?)) Self::new(chain, std::env::var("ETHERSCAN_API_KEY")?)
} }
pub fn etherscan_api_url(&self) -> &Url { pub fn etherscan_api_url(&self) -> &Url {
@ -157,3 +147,17 @@ struct Query<'a, T: Serialize> {
#[serde(flatten)] #[serde(flatten)]
other: T, other: T,
} }
#[cfg(test)]
mod tests {
use crate::{Client, EtherscanError};
use ethers_core::types::Chain;
#[test]
fn chain_not_supported() {
let err = Client::new_from_env(Chain::XDai).unwrap_err();
assert!(matches!(err, EtherscanError::ChainNotSupported(_)));
assert_eq!(err.to_string(), "chain XDai not supported");
}
}

View File

@ -22,7 +22,11 @@ impl Client {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("txhash", tx_hash.as_ref()); map.insert("txhash", tx_hash.as_ref());
let query = self.create_query("transaction", "getstatus", map); let query = self.create_query(
"transaction",
"getstatus",
HashMap::from([("txhash", tx_hash.as_ref())]),
);
let response: Response<ContractExecutionStatus> = self.get_json(&query).await?; let response: Response<ContractExecutionStatus> = self.get_json(&query).await?;
if response.result.is_error == "0" { if response.result.is_error == "0" {
@ -36,10 +40,11 @@ impl Client {
/// Returns the status of a transaction execution: `false` for failed and `true` for successful /// Returns the status of a transaction execution: `false` for failed and `true` for successful
pub async fn check_transaction_receipt_status(&self, tx_hash: impl AsRef<str>) -> Result<()> { pub async fn check_transaction_receipt_status(&self, tx_hash: impl AsRef<str>) -> Result<()> {
let mut map = HashMap::new(); let query = self.create_query(
map.insert("txhash", tx_hash.as_ref()); "transaction",
"gettxreceiptstatus",
let query = self.create_query("transaction", "gettxreceiptstatus", map); HashMap::from([("txhash", tx_hash.as_ref())]),
);
let response: Response<TransactionReceiptStatus> = self.get_json(&query).await?; let response: Response<TransactionReceiptStatus> = self.get_json(&query).await?;
match response.result.status.as_str() { match response.result.status.as_str() {