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:
parent
5779a3cdaf
commit
cba1a85483
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -48,5 +48,9 @@ pub use txpool::*;
|
|||
mod trace;
|
||||
pub use trace::*;
|
||||
|
||||
mod chain;
|
||||
pub use chain::*;
|
||||
|
||||
mod proof;
|
||||
|
||||
pub use proof::*;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue