#![expect(clippy::doc_markdown)]
use degenbot_core::op_warn;
use alloy::eips::BlockId;
use alloy::network::Ethereum;
use alloy::primitives::U256;
use alloy::providers::{Provider, RootProvider};
use degenbot_substrate::SimAnchorOracle;
use parking_lot::RwLock;
use revm::database::CacheDB;
use revm::database_interface::WrapDatabaseAsync;
use std::sync::Arc;
use super::state_override::SimulationOverrideParams;
use revm::{MainBuilder, MainContext};
#[derive(Clone)]
pub struct ArcDynProviderEthereum(Arc<dyn Provider<Ethereum>>);
impl Provider<Ethereum> for ArcDynProviderEthereum {
fn root(&self) -> &RootProvider<Ethereum> {
self.0.root()
}
}
pub type ProductionBlockDb<'a> = CacheDB<
super::WarmCodeCache<
super::BotStateDb<
'a,
WrapDatabaseAsync<revm::database::AlloyDB<Ethereum, ArcDynProviderEthereum>>,
>,
>,
>;
pub type ScratchDb<'a> = CacheDB<
super::WarmCodeCache<
super::frame_replay::CountingFrameDb<
super::BotStateDb<
'a,
WrapDatabaseAsync<revm::database::AlloyDB<Ethereum, ArcDynProviderEthereum>>,
>,
>,
>,
>;
pub type BlockEvm<'a> = revm::MainnetEvm<
revm::handler::MainnetContext<ProductionBlockDb<'a>>,
super::inspectors::SimInspector,
>;
pub struct BlockSimHandle<'a> {
evm: BlockEvm<'a>,
provider_arc: Arc<dyn Provider<Ethereum>>,
rpc_url: String,
base_fee_next: u128,
current_block: u64,
block_timestamp: u64,
oracle: &'a dyn SimAnchorOracle,
warm_cache: Arc<RwLock<super::WarmCodeCacheInner>>,
storage_memo: Option<std::sync::Arc<super::StorageMemo>>,
verify_divergence: bool,
scratch: Option<super::frame_replay::ScratchEvm<super::ScratchDb<'a>>>,
}
impl<'a> BlockSimHandle<'a> {
#[expect(clippy::too_many_arguments)] #[must_use]
pub fn build(
provider: °enbot_rpc::provider::AlloyProvider,
base_fee_next: u128,
current_block: u64,
block_timestamp: u64,
override_params: &SimulationOverrideParams,
oracle: &'a dyn SimAnchorOracle,
warm_cache: &Arc<RwLock<super::WarmCodeCacheInner>>,
storage_memo: Option<&std::sync::Arc<super::StorageMemo>>,
verify_divergence: bool,
) -> Option<Self> {
let (built, build_dur) = super::sim_metrics::timed(|| {
Self::build_inner(
provider,
base_fee_next,
current_block,
block_timestamp,
override_params,
oracle,
warm_cache,
storage_memo,
verify_divergence,
)
});
super::sim_metrics::record_handle_build(build_dur);
built
}
#[expect(clippy::too_many_arguments)] fn build_inner(
provider: °enbot_rpc::provider::AlloyProvider,
base_fee_next: u128,
current_block: u64,
block_timestamp: u64,
override_params: &SimulationOverrideParams,
oracle: &'a dyn SimAnchorOracle,
warm_cache: &Arc<RwLock<super::WarmCodeCacheInner>>,
storage_memo: Option<&std::sync::Arc<super::StorageMemo>>,
verify_divergence: bool,
) -> Option<Self> {
let alloy_db = revm::database::AlloyDB::new(
ArcDynProviderEthereum(provider.provider_arc()),
BlockId::Number(current_block.into()),
);
let Some(wrap_db) = WrapDatabaseAsync::new(alloy_db) else {
op_warn!(
domain = sim,
"BlockSimHandle: no ambient multi-threaded tokio runtime — \
WrapDatabaseAsync unavailable; block sim disabled"
);
return None;
};
let bot_state_db = super::BotStateDb::new_with_code_probe(
oracle,
wrap_db,
provider.rpc_url(),
current_block,
)
.with_storage_memo_opt(storage_memo)
.with_divergence_probe(verify_divergence);
let warm_code_cache =
super::WarmCodeCache::with_owner(Arc::clone(warm_cache), current_block, bot_state_db);
let mut cache_db = CacheDB::new(warm_code_cache);
if let Err(err) =
super::state_override::apply_simulation_overrides(&mut cache_db, override_params)
{
op_warn!(domain = sim, %err, "BlockSimHandle: state-override application failed");
return None;
}
let mut revm_ctx = revm::context::Context::mainnet();
revm_ctx.cfg.disable_nonce_check = true;
let mut evm = revm_ctx
.with_db(cache_db)
.build_mainnet_with_inspector(super::inspectors::SimInspector::default());
evm.ctx.modify_block(|block| {
block.basefee = u64::try_from(base_fee_next).unwrap_or(u64::MAX);
block.number = U256::from(current_block);
block.timestamp = U256::from(block_timestamp);
});
Some(Self {
evm,
provider_arc: provider.provider_arc(),
rpc_url: provider.rpc_url().to_string(),
base_fee_next,
current_block,
block_timestamp,
oracle,
warm_cache: Arc::clone(warm_cache),
storage_memo: storage_memo.cloned(),
verify_divergence,
scratch: None,
})
}
#[must_use]
pub fn evm_mut(&mut self) -> &mut BlockEvm<'a> {
&mut self.evm
}
pub fn scratch_evm(
&mut self,
) -> Option<&mut super::frame_replay::ScratchEvm<super::ScratchDb<'a>>> {
if self.scratch.is_none() {
let alloy_db = revm::database::AlloyDB::new(
ArcDynProviderEthereum(Arc::clone(&self.provider_arc)),
BlockId::Number(self.current_block.into()),
);
let counter = std::sync::Arc::new(super::frame_replay::FrameRpcCounter::default());
self.scratch = WrapDatabaseAsync::new(alloy_db).map(|wrap_db| {
let bot_state_db = super::BotStateDb::new_with_code_probe(
self.oracle,
wrap_db,
&self.rpc_url,
self.current_block,
)
.with_storage_memo_opt(self.storage_memo.as_ref())
.with_divergence_probe(self.verify_divergence);
let counted_cold_path = super::frame_replay::CountingFrameDb::new(
bot_state_db,
std::sync::Arc::clone(&counter),
);
let warm_code_cache = super::WarmCodeCache::with_owner(
Arc::clone(&self.warm_cache),
self.current_block,
counted_cold_path,
);
let ext = CacheDB::new(warm_code_cache);
super::frame_replay::ScratchEvm::with_counter(
ext,
super::frame_replay::ScratchBlock {
number: self.current_block.saturating_add(1),
timestamp: self.block_timestamp,
base_fee_next: self.base_fee_next,
},
counter,
)
});
}
self.scratch.as_mut()
}
}