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::{
|
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.
|
||||||
|
|
|
@ -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;
|
mod trace;
|
||||||
pub use trace::*;
|
pub use trace::*;
|
||||||
|
|
||||||
|
mod chain;
|
||||||
|
pub use chain::*;
|
||||||
|
|
||||||
mod proof;
|
mod proof;
|
||||||
|
|
||||||
pub use proof::*;
|
pub use proof::*;
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue