use crate::common::error::AppError;
use crate::network::provider::HttpProvider;
use alloy::providers::Provider;
use alloy::rpc::types::eth::simulate::{SimBlock, SimulatePayload};
use alloy::rpc::types::eth::state::StateOverride;
use alloy::rpc::types::eth::Transaction;
use alloy::rpc::types::eth::TransactionRequest;
#[derive(Debug, Clone)]
pub struct SimulationOutcome {
pub success: bool,
pub gas_used: u64,
pub return_data: Vec<u8>,
}
#[derive(Clone)]
pub struct Simulator {
provider: HttpProvider,
}
impl Simulator {
pub fn new(provider: HttpProvider) -> Self {
Self { provider }
}
pub async fn simulate_transaction(
&self,
tx: &Transaction,
) -> Result<SimulationOutcome, AppError> {
let req = tx.clone().into_request();
self.simulate_request(req, None).await
}
pub async fn simulate_request(
&self,
req: TransactionRequest,
state_override: Option<StateOverride>,
) -> Result<SimulationOutcome, AppError> {
if state_override.is_some() {
let block = SimBlock {
block_overrides: None,
state_overrides: state_override.clone(),
calls: vec![req.clone()],
};
let payload = SimulatePayload {
block_state_calls: vec![block],
trace_transfers: false,
validation: false,
return_full_transactions: false,
};
if let Ok(simulated) = self.provider.simulate(&payload).await {
if let Some(call) = simulated.first().and_then(|b| b.calls.first()) {
return Ok(SimulationOutcome {
success: call.error.is_none() && call.status,
gas_used: call.gas_used,
return_data: call.return_data.to_vec(),
});
}
}
}
let gas_used = match self.provider.estimate_gas(req.clone()).await {
Ok(g) => g,
Err(e) => {
return Ok(SimulationOutcome {
success: false,
gas_used: 0,
return_data: format!("estimate_gas failed: {e}").into_bytes(),
})
}
};
let call_res = self.provider.call(req).await;
let (success, return_data) = match call_res {
Ok(bytes) => (true, bytes.to_vec()),
Err(_) => (false, Vec::new()),
};
Ok(SimulationOutcome {
success,
gas_used,
return_data,
})
}
pub async fn simulate_bundle(
&self,
txs: &[Transaction],
state_override: Option<StateOverride>,
) -> Result<Vec<SimulationOutcome>, AppError> {
let reqs: Vec<TransactionRequest> = txs.iter().cloned().map(|t| t.into_request()).collect();
self.simulate_bundle_requests(&reqs, state_override).await
}
pub async fn simulate_bundle_requests(
&self,
txs: &[TransactionRequest],
state_override: Option<StateOverride>,
) -> Result<Vec<SimulationOutcome>, AppError> {
if txs.is_empty() {
return Ok(Vec::new());
}
let calls = txs.to_vec();
let block = SimBlock {
block_overrides: None,
state_overrides: state_override.clone(),
calls,
};
let payload = SimulatePayload {
block_state_calls: vec![block],
trace_transfers: false,
validation: false,
return_full_transactions: false,
};
match self.provider.simulate(&payload).await {
Ok(blocks) => {
let mut out = Vec::new();
for blk in blocks {
for tx in blk.calls {
let success = tx.error.is_none();
let gas_used = tx.gas_used;
let return_data = tx.return_data.to_vec();
out.push(SimulationOutcome {
success,
gas_used,
return_data,
});
}
}
return Ok(out);
}
Err(_) => {
let mut out = Vec::new();
for tx in txs {
out.push(
self.simulate_request(tx.clone(), state_override.clone())
.await?,
);
}
Ok(out)
}
}
}
}