Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "trevm"
version = "0.20.11"
version = "0.23.0"
rust-version = "1.83.0"
edition = "2021"
authors = ["init4"]
Expand All @@ -26,24 +26,37 @@ use-self = "warn"
option-if-let-else = "warn"
redundant-clone = "warn"

[[example]]
name = "basic_transact"

[[example]]
name = "fork_ref_transact"
required-features = ["alloy-db"]

[dependencies]
alloy = { version = "0.12.6", default-features = false, features = ["consensus", "rpc-types-mev", "eips", "k256", "std", "rlp", "sol-types"] }
alloy = { version = "1.0.5", default-features = false, features = [
"consensus",
"rpc-types-mev",
"eips",
"k256",
"std",
"rlp",
"sol-types",
] }

revm = { version = "20.0.0", default-features = false }
revm = { version = "23.1.0", default-features = false }

dashmap = { version = "6.1.0", optional = true }
tracing = { version = "0.1.41", optional = true}
tracing = { version = "0.1.41", optional = true }
thiserror = "2.0.11"

tokio = { version = "1.44", optional = true }

[dev-dependencies]
revm = { version = "20.0.0", features = [
"serde-json",
"std",
"alloydb",
] }
revm = { version = "23.1.0", features = ["serde-json", "std", "alloydb"] }
trevm = { path = ".", features = ["test-utils"] }

alloy = { version = "0.12.6", features = ["providers"]}
alloy = { version = "1.0.5", features = ["providers", "transports"] }

# misc
eyre = "0.6"
Expand All @@ -63,6 +76,8 @@ default = [
"revm/secp256k1",
]

alloy-db = ["dep:tokio"]

call = ["optional_eip3607", "optional_no_base_fee"]

concurrent-db = ["dep:dashmap"]
Expand Down
15 changes: 5 additions & 10 deletions examples/fork_ref_transact.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! This example demonstrates how to query storage slots of a contract, using
//! [`AlloyDB`].

//! This example is currently disabled while waiting for revm @ 14.0.4
//! [`AlloyDb`].

use alloy::{
eips::BlockId,
Expand All @@ -11,10 +9,7 @@ use alloy::{
sol_types::SolCall,
};
use revm::{context::TxEnv, database::WrapDatabaseAsync};
use trevm::{
revm::database::{AlloyDB, CacheDB},
NoopBlock, NoopCfg, TrevmBuilder, Tx,
};
use trevm::{db::alloy::AlloyDb, revm::database::CacheDB, NoopBlock, NoopCfg, TrevmBuilder, Tx};

sol! {
#[allow(missing_docs)]
Expand Down Expand Up @@ -42,7 +37,7 @@ async fn main() -> eyre::Result<()> {
// create ethers client and wrap it in Arc<M>
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";

let client = ProviderBuilder::new().on_http(rpc_url.parse()?);
let client = ProviderBuilder::new().connect_http(rpc_url.parse()?);

// ----------------------------------------------------------- //
// Storage slots of UniV2Pair contract //
Expand All @@ -57,7 +52,7 @@ async fn main() -> eyre::Result<()> {
// =========================================================== //

// initialize new AlloyDB
let alloydb = WrapDatabaseAsync::new(AlloyDB::new(client, BlockId::default())).unwrap();
let alloydb = WrapDatabaseAsync::new(AlloyDb::new(client, BlockId::default())).unwrap();

// initialise empty in-memory-db
let cache_db = CacheDB::new(alloydb);
Expand All @@ -78,7 +73,7 @@ async fn main() -> eyre::Result<()> {
let output = evm.output().expect("Execution halted");

// decode bytes to reserves + ts via alloy's abi decode
let return_vals = getReservesCall::abi_decode_returns(output, true)?;
let return_vals = getReservesCall::abi_decode_returns_validate(output)?;

// Print emulated getReserves() call output
println!("Reserve0: {:#?}", return_vals.reserve0);
Expand Down
154 changes: 154 additions & 0 deletions src/db/alloy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use alloy::{
eips::BlockId,
primitives::{StorageValue, U256},
providers::{
network::{primitives::HeaderResponse, BlockResponse},
Network, Provider,
},
transports::TransportError,
};
use core::error::Error;
use revm::{
database_interface::{async_db::DatabaseAsyncRef, DBErrorMarker},
primitives::{Address, B256},
state::{AccountInfo, Bytecode},
};
use std::fmt::Display;

/// A type alias for the storage key used in the database.
/// We use this instead of alloy's [`alloy::primitives::StorageKey`] as Revm requires
/// the actual type to be an [`U256`] instead of a [`B256`].
pub type StorageKey = U256;

/// An error that can occur when using [`AlloyDb`].
#[derive(Debug)]
pub struct DBTransportError(pub TransportError);

impl DBErrorMarker for DBTransportError {}

impl Display for DBTransportError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Transport error: {}", self.0)
}
}

impl Error for DBTransportError {}

impl From<TransportError> for DBTransportError {
fn from(e: TransportError) -> Self {
Self(e)
}
}

/// An alloy-powered REVM [`Database`][revm::database_interface::Database].
///
/// When accessing the database, it'll use the given provider to fetch the corresponding account's data.
#[derive(Debug)]
pub struct AlloyDb<N: Network, P: Provider<N>> {
/// The provider to fetch the data from.
provider: P,
/// The block number on which the queries will be based on.
block_number: BlockId,
_marker: core::marker::PhantomData<fn() -> N>,
}

impl<N: Network, P: Provider<N>> AlloyDb<N, P> {
/// Creates a new AlloyDB instance, with a [`Provider`] and a block.
pub fn new(provider: P, block_number: BlockId) -> Self {
Self { provider, block_number, _marker: core::marker::PhantomData }
}

/// Sets the block number on which the queries will be based on.
pub const fn set_block_number(&mut self, block_number: BlockId) {
self.block_number = block_number;
}
}

impl<N: Network, P: Provider<N>> DatabaseAsyncRef for AlloyDb<N, P> {
type Error = DBTransportError;

async fn basic_async_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let nonce = self.provider.get_transaction_count(address).block_id(self.block_number);
let balance = self.provider.get_balance(address).block_id(self.block_number);
let code = self.provider.get_code_at(address).block_id(self.block_number);

let (nonce, balance, code) = tokio::join!(nonce, balance, code,);

let balance = balance?;
let code = Bytecode::new_raw(code?.0.into());
let code_hash = code.hash_slow();
let nonce = nonce?;

Ok(Some(AccountInfo::new(balance, nonce, code_hash, code)))
}

async fn block_hash_async_ref(&self, number: u64) -> Result<B256, Self::Error> {
let block = self
.provider
// We know number <= u64::MAX, so we can safely convert it to u64
.get_block_by_number(number.into())
.await?;
// If the number is given, the block is supposed to be finalized, so unwrapping is safe.
Ok(B256::new(*block.unwrap().header().hash()))
}

async fn code_by_hash_async_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
panic!("This should not be called, as the code is already loaded");
// This is not needed, as the code is already loaded with basic_ref
}

async fn storage_async_ref(
&self,
address: Address,
index: StorageKey,
) -> Result<StorageValue, Self::Error> {
Ok(self.provider.get_storage_at(address, index).block_id(self.block_number).await?)
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy::providers::ProviderBuilder;
use revm::database_interface::{DatabaseRef, WrapDatabaseAsync};

#[test]
#[ignore = "flaky RPC"]
fn can_get_basic() {
let client = ProviderBuilder::new().connect_http(
"https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27".parse().unwrap(),
);
let alloydb = AlloyDb::new(client, BlockId::from(16148323));
let wrapped_alloydb = WrapDatabaseAsync::new(alloydb).unwrap();

// ETH/USDT pair on Uniswap V2
let address: Address = "0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852".parse().unwrap();

let acc_info = wrapped_alloydb.basic_ref(address).unwrap().unwrap();
assert!(acc_info.exists());
}
}

// This code has been reproduced from the original AlloyDB implementation
// contained in revm.
// <https://github.com/bluealloy/revm>
// The original license is included below:
//
// MIT License
// Copyright (c) 2021-2025 draganrakita
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
4 changes: 4 additions & 0 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ pub use traits::{ArcUpgradeError, CachingDb, StateAcc, TryCachingDb, TryStateAcc
/// Cache-on-write database. A memory cache that caches only on write, not on
/// read. Intended to wrap some other caching database.
pub mod cow;

#[cfg(feature = "alloy-db")]
/// Alloy-powered revm Database implementation that fetches data over the network.
pub mod alloy;
15 changes: 12 additions & 3 deletions src/driver/alloy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use crate::{
trevm_bail, trevm_ensure, trevm_try, Block, BundleDriver, DriveBundleResult,
};
use alloy::{
consensus::{Transaction, TxEip4844Variant, TxEnvelope},
consensus::{
crypto::RecoveryError, transaction::SignerRecoverable, Transaction, TxEip4844Variant,
TxEnvelope,
},
eips::{eip2718::Decodable2718, BlockNumberOrTag},
primitives::{bytes::Buf, keccak256, Address, Bytes, TxKind, U256},
rpc::types::mev::{
Expand Down Expand Up @@ -35,7 +38,7 @@ pub enum BundleError<Db: Database> {
/// An error occurred while decoding a transaction contained in the bundle.
TransactionDecodingError(alloy::eips::eip2718::Eip2718Error),
/// An error occurred while recovering the sender of a transaction.
TransactionSenderRecoveryError(alloy::primitives::SignatureError),
TransactionSenderRecoveryError(alloy::consensus::crypto::RecoveryError),
/// An error occurred while running the EVM.
EVMError {
/// The error that occurred while running the EVM.
Expand Down Expand Up @@ -71,7 +74,7 @@ impl<Db: Database> From<alloy::eips::eip2718::Eip2718Error> for BundleError<Db>

impl<Db: Database> From<alloy::primitives::SignatureError> for BundleError<Db> {
fn from(err: alloy::primitives::SignatureError) -> Self {
Self::TransactionSenderRecoveryError(err)
Self::TransactionSenderRecoveryError(err.into())
}
}

Expand All @@ -91,6 +94,12 @@ impl<Db: Database> std::error::Error for BundleError<Db> {
}
}

impl<Db: Database> From<RecoveryError> for BundleError<Db> {
fn from(err: RecoveryError) -> Self {
Self::TransactionSenderRecoveryError(err)
}
}

impl<Db: Database> core::fmt::Debug for BundleError<Db> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Expand Down
Loading