oxidized_builder/core/
simulation.rs1use crate::common::error::AppError;
2use crate::network::provider::HttpProvider;
3use alloy::providers::Provider;
4use alloy::rpc::types::eth::simulate::{SimBlock, SimulatePayload};
5use alloy::rpc::types::eth::state::StateOverride;
6use alloy::rpc::types::eth::Transaction;
7use alloy::rpc::types::eth::TransactionRequest;
8
9#[derive(Debug, Clone)]
10pub struct SimulationOutcome {
11 pub success: bool,
12 pub gas_used: u64,
13 pub return_data: Vec<u8>,
14}
15
16#[derive(Clone)]
17pub struct Simulator {
18 provider: HttpProvider,
19}
20
21impl Simulator {
22 pub fn new(provider: HttpProvider) -> Self {
23 Self { provider }
24 }
25
26 pub async fn simulate_transaction(
27 &self,
28 tx: &Transaction,
29 ) -> Result<SimulationOutcome, AppError> {
30 let req = tx.clone().into_request();
31 self.simulate_request(req, None).await
32 }
33
34 pub async fn simulate_request(
35 &self,
36 req: TransactionRequest,
37 state_override: Option<StateOverride>,
38 ) -> Result<SimulationOutcome, AppError> {
39 if state_override.is_some() {
40 let block = SimBlock {
41 block_overrides: None,
42 state_overrides: state_override.clone(),
43 calls: vec![req.clone()],
44 };
45 let payload = SimulatePayload {
46 block_state_calls: vec![block],
47 trace_transfers: false,
48 validation: false,
49 return_full_transactions: false,
50 };
51
52 if let Ok(simulated) = self.provider.simulate(&payload).await {
53 if let Some(call) = simulated.first().and_then(|b| b.calls.first()) {
54 return Ok(SimulationOutcome {
55 success: call.error.is_none() && call.status,
56 gas_used: call.gas_used,
57 return_data: call.return_data.to_vec(),
58 });
59 }
60 }
61 }
62
63 let gas_used = match self.provider.estimate_gas(req.clone()).await {
64 Ok(g) => g,
65 Err(e) => {
66 return Ok(SimulationOutcome {
67 success: false,
68 gas_used: 0,
69 return_data: format!("estimate_gas failed: {e}").into_bytes(),
70 })
71 }
72 };
73
74 let call_res = self.provider.call(req).await;
75
76 let (success, return_data) = match call_res {
77 Ok(bytes) => (true, bytes.to_vec()),
78 Err(_) => (false, Vec::new()),
79 };
80
81 Ok(SimulationOutcome {
82 success,
83 gas_used,
84 return_data,
85 })
86 }
87
88 pub async fn simulate_bundle(
89 &self,
90 txs: &[Transaction],
91 state_override: Option<StateOverride>,
92 ) -> Result<Vec<SimulationOutcome>, AppError> {
93 let reqs: Vec<TransactionRequest> = txs.iter().cloned().map(|t| t.into_request()).collect();
94 self.simulate_bundle_requests(&reqs, state_override).await
95 }
96
97 pub async fn simulate_bundle_requests(
98 &self,
99 txs: &[TransactionRequest],
100 state_override: Option<StateOverride>,
101 ) -> Result<Vec<SimulationOutcome>, AppError> {
102 if txs.is_empty() {
103 return Ok(Vec::new());
104 }
105
106 let calls = txs.to_vec();
107 let block = SimBlock {
108 block_overrides: None,
109 state_overrides: state_override.clone(),
110 calls,
111 };
112 let payload = SimulatePayload {
113 block_state_calls: vec![block],
114 trace_transfers: false,
115 validation: false,
116 return_full_transactions: false,
117 };
118
119 match self.provider.simulate(&payload).await {
120 Ok(blocks) => {
121 let mut out = Vec::new();
122 for blk in blocks {
123 for tx in blk.calls {
124 let success = tx.error.is_none();
125 let gas_used = tx.gas_used;
126 let return_data = tx.return_data.to_vec();
127 out.push(SimulationOutcome {
128 success,
129 gas_used,
130 return_data,
131 });
132 }
133 }
134 return Ok(out);
135 }
136 Err(_) => {
137 let mut out = Vec::new();
138 for tx in txs {
139 out.push(
140 self.simulate_request(tx.clone(), state_override.clone())
141 .await?,
142 );
143 }
144 Ok(out)
145 }
146 }
147 }
148}