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::{
abi::{Detokenize, Function, Token},
types::{Address, BlockNumber, Bytes, NameOrAddress, TxHash, U256},
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, U256},
};
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
/// Multicall smart contract addresses as values
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");
m.insert(U256::from(1u8), addr);
// rinkeby
let addr =
Address::from_str("42ad527de7d4e9d9d011ac45b31d8551f8fe9821").expect("Decoding failed");
m.insert(U256::from(4u8), addr);
// goerli
let addr =
Address::from_str("77dca2c955b15e9de4dbbcf1246b4b85b651e50e").expect("Decoding failed");
m.insert(U256::from(5u8), addr);
// kovan
let addr =
Address::from_str("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a").expect("Decoding failed");
m.insert(U256::from(42u8), addr);
// xdai
let addr =
Address::from_str("b5b692a88bdfc81ca69dcb1d924f59f0413a602a").expect("Decoding failed");
m.insert(U256::from(100u8), addr);
m
[
(
Chain::Mainnet.into(),
decode_address("eefba1e63905ef1d7acba5a8513c70307c1ce441"),
),
(
Chain::Rinkeby.into(),
decode_address("42ad527de7d4e9d9d011ac45b31d8551f8fe9821"),
),
(
Chain::Goerli.into(),
decode_address("77dca2c955b15e9de4dbbcf1246b4b85b651e50e"),
),
(
Chain::Kovan.into(),
decode_address("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a"),
),
(
Chain::XDai.into(),
decode_address("b5b692a88bdfc81ca69dcb1d924f59f0413a602a"),
),
]
.into()
});
/// 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;
pub use trace::*;
mod chain;
pub use chain::*;
mod proof;
pub use proof::*;

View File

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

View File

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

View File

@ -5,15 +5,15 @@ mod errors;
mod transaction;
use errors::EtherscanError;
use ethers_core::abi::Address;
use ethers_core::{abi::Address, types::Chain};
use reqwest::{header, Url};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{borrow::Cow, fmt};
use std::borrow::Cow;
pub type Result<T> = std::result::Result<T, EtherscanError>;
/// The Etherscan.io API client.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Client {
/// Client that executes HTTP requests
client: reqwest::Client,
@ -25,47 +25,37 @@ pub struct Client {
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 {
/// 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 {
Chain::Mainnet => (
Url::parse("https://api.etherscan.io/api"),
Url::parse("https://etherscan.io"),
),
Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => (
Url::parse(&format!("https://api-{}.etherscan.io/api", chain)),
Url::parse(&format!("https://{}.etherscan.io", chain)),
),
Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => {
let chain_name = chain.to_string().to_lowercase();
(
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(),
api_key: api_key.into(),
etherscan_api_url: etherscan_api_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
/// from ETHERSCAN_API_KEY environment variable
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 {
@ -157,3 +147,17 @@ struct Query<'a, T: Serialize> {
#[serde(flatten)]
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();
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?;
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
pub async fn check_transaction_receipt_status(&self, tx_hash: impl AsRef<str>) -> Result<()> {
let mut map = HashMap::new();
map.insert("txhash", tx_hash.as_ref());
let query = self.create_query("transaction", "gettxreceiptstatus", map);
let query = self.create_query(
"transaction",
"gettxreceiptstatus",
HashMap::from([("txhash", tx_hash.as_ref())]),
);
let response: Response<TransactionReceiptStatus> = self.get_json(&query).await?;
match response.result.status.as_str() {