Skip to content

Commit

Permalink
replace deprecated ethers and web3 with modern alloy-rs
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirill-K-1 committed Oct 26, 2024
1 parent 7f1cade commit daadfb4
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 210 deletions.
14 changes: 6 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ futures-util = "0.3"
async-trait = "0.1"

# Ethereum
ethers = "2.0.14"
ethers-contract-derive = "2.0"
ethabi = "18.0.0"
ethabi-derive = "16.0.0"
ethereum-types = "0.14"
jsonrpc-core = "18.0.0"
web3 = { version = "0.19" }
ethers-providers = "2"
alloy = { version = "0.5.4", features = ["full", "json-rpc", "sol-types"] }
alloy-sol-types = "0.8.9"

# Crypto
k256 = { version = "0.13" }
Expand All @@ -32,6 +29,7 @@ cipher = "0.4"

# Web
reqwest = { version = "0.12", features = ["rustls-tls", "json", "deflate", "stream", "multipart"] }
url = { version = "2.5.2", features = ["serde"] }

# SerDe
serde = { version = "1.0", features = ["derive"] }
Expand Down Expand Up @@ -66,6 +64,6 @@ clap = { version = "4.5", features = ["derive"] }
[dev-dependencies]
assert_matches = "1.5"

[patch.crates-io.ethers]
git = "https://github.com/ZelionD/ethers-rs.git"
branch = "feature/parse-tuple-keyword-in-func-params"
[patch.crates-io.alloy-sol-type-parser]
git = "https://github.com/ZelionD/core.git"
branch = "feature/parse-full-signature-tuples"
6 changes: 3 additions & 3 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
"main": {
"name": "main",
"domain": "ambrosus.io",
"rpc": "https://network.ambrosus.io",
"rpc": "https://rpc.airdao.io",
"chainspec": "https://chainspec.ambrosus.io",
"explorerUrl": "https://airdao.io"
},
"test": {
"name": "test",
"domain": "ambrosus-test.io",
"rpc": "https://network.ambrosus-test.io",
"rpc": "https://testnet-rpc.airdao.io",
"chainspec": "https://chainspec.ambrosus-test.io",
"explorerUrl": "https://testnet.airdao.io"
},
"dev": {
"name": "dev",
"domain": "ambrosus-dev.io",
"rpc": "https://network.ambrosus-dev.io",
"rpc": "https://devnet-rpc.airdao.io",
"chainspec": "https://chainspec.ambrosus-dev.io",
"explorerUrl": "https://devnet.airdao.io"
}
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct Config {
#[serde(rename_all = "camelCase")]
pub struct Network {
pub domain: String,
pub rpc: String,
pub rpc: reqwest::Url,
pub chainspec: String,
pub explorer_url: String,
pub name: String,
Expand Down
60 changes: 16 additions & 44 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use ethabi::{Contract, Function};
use ethereum_types::Address;
use ethers::abi::HumanReadableParser;
use serde::Deserialize;
use alloy::{
json_abi::{Function, JsonAbi as Contract},
primitives::Address,
};
use anyhow::anyhow;
use serde::{de, Deserialize};

#[derive(Debug)]
pub struct EthContract {
pub address: Address,
inner: ethers::abi::Contract,
pub inner: Contract,
}

impl EthContract {
pub fn function(&self, name: &str) -> anyhow::Result<&Function> {
self.inner.function(name).map_err(anyhow::Error::from)
self.inner
.function(name)
.and_then(|functions| functions.first())
.ok_or_else(|| anyhow!("Function {name} not found in contract abi!"))
}
}

Expand All @@ -21,48 +26,15 @@ impl<'de> Deserialize<'de> for EthContract {
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Inner {
struct Inner<'a> {
address: Address,
abi: Vec<String>,
#[serde(borrow)]
abi: Vec<&'a str>,
}

let inner = Inner::deserialize(deserializer)?;
let mut contract_abi = Contract::default();

// Workaround due to issues in ethers parser. Ignore errors
inner.abi.iter().for_each(|input| {
if input.starts_with("constructor ") {
contract_abi.constructor = HumanReadableParser::parse_constructor(input).ok();
} else if input.starts_with("function ") {
if let Ok(func) = HumanReadableParser::parse_function(input) {
contract_abi
.functions
.entry(func.name.clone())
.or_default()
.push(func);
}
} else if input.starts_with("event ") {
if let Ok(event) = HumanReadableParser::parse_event(input) {
contract_abi
.events
.entry(event.name.clone())
.or_default()
.push(event);
}
} else if input.starts_with("error ") {
if let Ok(error) = HumanReadableParser::parse_error(input) {
contract_abi
.errors
.entry(error.name.clone())
.or_default()
.push(error);
}
} else if input.starts_with("fallback(") {
contract_abi.fallback = true;
} else if input.starts_with("receive(") {
contract_abi.receive = true;
}
});
let contract_abi = Contract::parse(inner.abi.into_iter())
.map_err(|e| de::Error::custom(format!("Failed to deserialize contract abi: {e:?}")))?;

Ok(Self {
address: inner.address,
Expand Down
27 changes: 15 additions & 12 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@ pub enum AppError {
/// Config error
#[error("Config error: {0}")]
Config(#[from] config::ConfigError),
/// Web3 error
#[error("Web3 error: {0}")]
Web3(#[from] web3::Error),
/// Regex error
#[error("RegExp error: {0}")]
Regexp(#[from] regex::Error),
/// Ethers crate parse error
#[error("Ethers abi parse error: {0}")]
EthersParse(#[from] ethers::abi::ParseError),
/// Ethers crate abi error
#[error("Ethers abi error: {0}")]
EthersAbi(#[from] ethers::abi::Error),
/// Ethers crate invalid output type error
#[error("Ethers invalid output type error: {0}")]
EthersOutputType(#[from] ethers::abi::InvalidOutputType),
/// Alloy abi error
#[error("Alloy abi error: {0}")]
AlloyAbi(#[from] alloy::dyn_abi::Error),
/// Alloy types error
#[error("Alloy types error: {0}")]
AlloyTypes(#[from] alloy::sol_types::Error),
/// Alloy rpc transport error
#[error("Alloy rpc transport error: {0}")]
AlloyRpcTransport(#[from] alloy::transports::TransportError),
/// Alloy contract error
#[error("Alloy contract error: {0}")]
AlloyContract(#[from] alloy::contract::Error),
/// Url parse error
#[error("Url parse error: {0}")]
UrlParse(#[from] url::ParseError),
/// Generic
#[error("{0:#}")]
Anyhow(#[from] anyhow::Error),
Expand Down
26 changes: 17 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod setup;
pub mod state;
pub mod utils;

use alloy::{network::AnyNetwork, providers::ProviderBuilder};
use anyhow::anyhow;
use clap::{Parser, Subcommand};
use console::style;
Expand Down Expand Up @@ -82,21 +83,28 @@ async fn run(config: &Config) -> Result<(), AppError> {

cliclack::log::step(MessageType::DockerStarted)?;

let web3_client_remote = web3::Web3::new(web3::transports::Http::new(&setup.network.rpc)?);
let web3_client_local = web3::Web3::new(web3::transports::Http::new(
std::env::var("PARITY_URL")
.as_deref()
.unwrap_or("http://127.0.0.1:8545"),
)?);
let provider_remote = ProviderBuilder::new()
.with_recommended_fillers()
.network::<AnyNetwork>()
.on_http(setup.network.rpc.clone());
let provider_local = ProviderBuilder::new()
.with_recommended_fillers()
.network::<AnyNetwork>()
.on_http(
std::env::var("PARITY_URL")
.as_deref()
.unwrap_or("http://127.0.0.1:8545")
.parse()?,
);

let mut check_status =
CheckStatusPhase::new(web3_client_remote.clone(), &setup.network, setup.address).await?;
CheckStatusPhase::new(provider_remote.clone(), &setup.network, setup.address).await?;
check_status.run().await?;

let mut actions_menu = ActionsMenuPhase::new(
config.discord_webhook_url.clone(),
web3_client_remote,
web3_client_local,
provider_remote,
provider_local,
);
loop {
if actions_menu.quit {
Expand Down
2 changes: 1 addition & 1 deletion src/messages.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use alloy::primitives::Address;
use chrono::{DateTime, Utc};
use ethereum_types::Address;
use std::{net::IpAddr, time::Duration};
use strum_macros::Display;

Expand Down
82 changes: 47 additions & 35 deletions src/phases/actions_menu.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use alloy::{
eips::{BlockId, BlockNumberOrTag},
network::{BlockResponse, HeaderResponse, Network},
primitives::U256,
providers::{
fillers::{FillProvider, TxFiller},
Provider,
},
rpc::types::{BlockTransactionsKind, SyncStatus},
transports::Transport,
};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use ethereum_types::U256;
use futures_util::{future::BoxFuture, FutureExt};
use serde::Deserialize;
use std::path::PathBuf;
use web3::{
types::{Block, BlockId, BlockNumber, SyncState},
Transport, Web3,
};

use super::Phase;
use crate::{
Expand All @@ -18,12 +24,16 @@ use crate::{
};
use messages::MessageType;

pub struct ActionsMenuPhase<T: Transport + Send + Sync>
where
<T as web3::Transport>::Out: Send,
pub struct ActionsMenuPhase<
F,
P: Provider<T, N> + Send + Sync + Clone,
T: Transport + Clone,
N: Network + Clone,
> where
F: TxFiller<N>,
{
web3_client_remote: Web3<T>,
web3_client_local: Web3<T>,
provider_remote: FillProvider<F, P, T, N>,
provider_local: FillProvider<F, P, T, N>,
client: reqwest::Client,
discord_webhook_url: String,
pub quit: bool,
Expand All @@ -47,41 +57,43 @@ struct DiscordError {
message: String,
}

impl<T: Transport + Send + Sync> ActionsMenuPhase<T>
impl<F, P: Provider<T, N> + Send + Sync + Clone, T: Transport + Clone, N: Network + Clone>
ActionsMenuPhase<F, P, T, N>
where
<T as web3::Transport>::Out: Send,
F: TxFiller<N>,
{
pub fn new(
discord_webhook_url: String,
web3_client_remote: Web3<T>,
web3_client_local: Web3<T>,
provider_remote: FillProvider<F, P, T, N>,
provider_local: FillProvider<F, P, T, N>,
) -> Self {
Self {
quit: false,
discord_webhook_url,
client: reqwest::Client::new(),
web3_client_remote,
web3_client_local,
provider_remote,
provider_local,
}
}

async fn check_sync(&self) -> Result<(), AppError> {
match self.web3_client_local.eth().syncing().await? {
SyncState::Syncing(state) => {
match self.provider_local.syncing().await? {
SyncStatus::Info(info) => {
cliclack::note(
"Sync check",
MessageType::Syncing {
progress: state
progress: info
.current_block
.saturating_sub(state.starting_block)
.saturating_sub(info.starting_block)
.saturating_mul(U256::from(100))
.checked_div(state.highest_block.saturating_sub(state.starting_block))
.checked_div(info.highest_block.saturating_sub(info.starting_block))
.unwrap_or(U256::from(100))
.as_u64(),
.try_into()
.map_err(anyhow::Error::from)?,
},
)?;
}
SyncState::NotSyncing => {
SyncStatus::None => {
cliclack::note("Sync check", MessageType::NotSyncing)?;
}
}
Expand All @@ -91,18 +103,17 @@ where

async fn check_fork(&self) -> Result<MessageType, AppError> {
match self
.web3_client_local
.eth()
.block(BlockId::Number(BlockNumber::Latest))
.provider_local
.get_block(
BlockId::Number(BlockNumberOrTag::Latest),
BlockTransactionsKind::Hashes,
)
.await?
{
Some(Block {
number: Some(block_number),
hash,
..
}) if matches!(
self.web3_client_remote.eth().block(BlockId::Number(BlockNumber::Number(block_number))).await?,
Some(remote_block) if remote_block.hash == hash) =>
Some(block)
if matches!(
self.provider_remote.get_block(BlockId::Number(BlockNumberOrTag::Number(block.header().number())), BlockTransactionsKind::Hashes).await?,
Some(remote_block) if remote_block.header().hash() == block.header().hash()) =>
{
Ok(MessageType::NotForked)
}
Expand Down Expand Up @@ -246,9 +257,10 @@ where
}
}

impl<T: Transport + Send + Sync> Phase for ActionsMenuPhase<T>
impl<F, P: Provider<T, N> + Send + Sync + Clone, T: Transport + Clone, N: Network + Clone> Phase
for ActionsMenuPhase<F, P, T, N>
where
<T as web3::Transport>::Out: Send,
F: TxFiller<N>,
{
fn run(&mut self) -> BoxFuture<'_, Result<(), error::AppError>> {
async {
Expand Down
Loading

0 comments on commit daadfb4

Please sign in to comment.