Skip to main content

alloy_rpc_types_eth/
simulate.rs

1//! 'eth_simulateV1' Request / Response types: <https://github.com/ethereum/execution-apis/pull/484>
2
3use crate::{
4    alloc::string::ToString, error::EthRpcErrorCode, state::StateOverride, Block, BlockOverrides,
5    Log, TransactionRequest,
6};
7use alloc::{string::String, vec::Vec};
8use alloy_primitives::{Bytes, U256};
9
10/// The maximum number of blocks that can be simulated in a single request,
11pub const MAX_SIMULATE_BLOCKS: u64 = 256;
12
13/// Represents a batch of calls to be simulated sequentially within a block.
14/// This struct includes block and state overrides as well as the transaction requests to be
15/// executed.
16#[derive(Clone, Debug)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(
19    feature = "serde",
20    serde(
21        rename_all = "camelCase",
22        bound(
23            deserialize = "TxReq: serde::Deserialize<'de>",
24            serialize = "TxReq: serde::Serialize"
25        )
26    )
27)]
28pub struct SimBlock<TxReq = TransactionRequest> {
29    /// Modifications to the default block characteristics.
30    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
31    pub block_overrides: Option<BlockOverrides>,
32    /// State modifications to apply before executing the transactions.
33    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
34    pub state_overrides: Option<StateOverride>,
35    /// A vector of transactions to be simulated.
36    #[cfg_attr(feature = "serde", serde(default = "Vec::new"))]
37    pub calls: Vec<TxReq>,
38}
39
40impl<TxReq> Default for SimBlock<TxReq> {
41    fn default() -> Self {
42        Self { block_overrides: None, state_overrides: None, calls: Vec::new() }
43    }
44}
45
46impl<TxReq> SimBlock<TxReq> {
47    /// Enables state overrides
48    pub fn with_state_overrides(mut self, overrides: StateOverride) -> Self {
49        self.state_overrides = Some(overrides);
50        self
51    }
52
53    /// Enables block overrides
54    pub fn with_block_overrides(mut self, overrides: BlockOverrides) -> Self {
55        self.block_overrides = Some(overrides);
56        self
57    }
58
59    /// Adds a call to the block.
60    pub fn call(mut self, call: TxReq) -> Self {
61        self.calls.push(call);
62        self
63    }
64
65    /// Adds multiple calls to the block.
66    pub fn extend_calls(mut self, calls: impl IntoIterator<Item = TxReq>) -> Self {
67        self.calls.extend(calls);
68        self
69    }
70
71    /// Returns the block's block number override if it exists.
72    pub fn block_number_override(&self) -> Option<U256> {
73        self.block_overrides.as_ref().and_then(|overrides| overrides.number)
74    }
75}
76
77/// Represents the result of simulating a block.
78#[derive(Clone, Debug, Default)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
81pub struct SimulatedBlock<B = Block> {
82    /// The simulated block.
83    #[cfg_attr(feature = "serde", serde(flatten))]
84    pub inner: B,
85    /// A vector of results for each call in the block.
86    pub calls: Vec<SimCallResult>,
87}
88
89/// Captures the outcome of a transaction simulation.
90/// It includes the return value, logs produced, gas used, and the status of the transaction.
91#[derive(Clone, Debug, Default)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
94pub struct SimCallResult {
95    /// The raw bytes returned by the transaction.
96    pub return_data: Bytes,
97    /// Logs generated during the execution of the transaction.
98    #[cfg_attr(feature = "serde", serde(default))]
99    pub logs: Vec<Log>,
100    /// The amount of gas used by the transaction.
101    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
102    pub gas_used: u64,
103    /// Maximum gas consumed during execution, before refunds.
104    #[cfg_attr(
105        feature = "serde",
106        serde(
107            default,
108            skip_serializing_if = "Option::is_none",
109            with = "alloy_serde::quantity::opt"
110        )
111    )]
112    pub max_used_gas: Option<u64>,
113    /// The final status of the transaction, typically indicating success or failure.
114    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
115    pub status: bool,
116    /// Error in case the call failed
117    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
118    pub error: Option<SimulateError>,
119}
120
121/// Simulation options for executing multiple blocks and transactions.
122///
123/// This struct configures how simulations are executed, including whether to trace token transfers,
124/// validate transaction sequences, and whether to return full transaction objects.
125#[derive(Clone, Debug)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[cfg_attr(
128    feature = "serde",
129    serde(
130        rename_all = "camelCase",
131        bound(
132            deserialize = "TxReq: serde::Deserialize<'de>",
133            serialize = "TxReq: serde::Serialize"
134        )
135    )
136)]
137pub struct SimulatePayload<TxReq = TransactionRequest> {
138    /// Array of block state calls to be executed at specific, optional block/state.
139    #[cfg_attr(feature = "serde", serde(default))]
140    pub block_state_calls: Vec<SimBlock<TxReq>>,
141    /// Flag to determine whether to trace ERC20/ERC721 token transfers within transactions.
142    #[cfg_attr(feature = "serde", serde(default))]
143    pub trace_transfers: bool,
144    /// Flag to enable or disable validation of the transaction sequence in the blocks.
145    #[cfg_attr(feature = "serde", serde(default))]
146    pub validation: bool,
147    /// Flag to decide if full transactions should be returned instead of just their hashes.
148    #[cfg_attr(feature = "serde", serde(default))]
149    pub return_full_transactions: bool,
150}
151
152impl<TxReq> Default for SimulatePayload<TxReq> {
153    fn default() -> Self {
154        Self {
155            block_state_calls: Vec::new(),
156            trace_transfers: false,
157            validation: false,
158            return_full_transactions: false,
159        }
160    }
161}
162
163impl<TxReq> SimulatePayload<TxReq> {
164    /// Adds a block to the simulation payload.
165    pub fn extend(mut self, block: SimBlock<TxReq>) -> Self {
166        self.block_state_calls.push(block);
167        self
168    }
169
170    /// Adds multiple blocks to the simulation payload.
171    pub fn extend_blocks(mut self, blocks: impl IntoIterator<Item = SimBlock<TxReq>>) -> Self {
172        self.block_state_calls.extend(blocks);
173        self
174    }
175
176    /// Enables tracing of token transfers.
177    pub const fn with_trace_transfers(mut self) -> Self {
178        self.trace_transfers = true;
179        self
180    }
181
182    /// Enables validation of the transaction sequence.
183    pub const fn with_validation(mut self) -> Self {
184        self.validation = true;
185        self
186    }
187
188    /// Enables returning full transactions.
189    pub const fn with_full_transactions(mut self) -> Self {
190        self.return_full_transactions = true;
191        self
192    }
193}
194
195/// The error response returned by the `eth_simulateV1` method.
196#[derive(Clone, Debug)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
198#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
199pub struct SimulateError {
200    /// Error code.
201    ///
202    /// Known values:
203    /// - [`Self::EXECUTION_REVERTED_CODE`] for `Execution reverted`
204    /// - [`Self::VM_EXECUTION_ERROR_CODE`] for `VM execution error`
205    pub code: i32,
206    /// Message error
207    pub message: String,
208    /// Data for the error, e.g. revert reason.
209    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
210    pub data: Option<Bytes>,
211}
212
213impl SimulateError {
214    /// `Execution reverted` error code.
215    pub const EXECUTION_REVERTED_CODE: i32 = EthRpcErrorCode::ExecutionError.code();
216    /// `VM execution error` error code.
217    pub const VM_EXECUTION_ERROR_CODE: i32 = -32015;
218    /// `Invalid params` error code.
219    pub const INVALID_PARAMS_ERROR_CODE: i32 = -32602;
220
221    /// Creates a new invalid params error.
222    pub fn invalid_params() -> Self {
223        Self {
224            code: Self::INVALID_PARAMS_ERROR_CODE,
225            message: "invalid params".to_string(),
226            data: None,
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use alloy_primitives::{bytes, Address, TxKind};
235    #[cfg(feature = "serde")]
236    use serde_json::json;
237    use similar_asserts::assert_eq;
238
239    #[test]
240    #[cfg(feature = "serde")]
241    fn test_deserialize_simulate_error_no_data() {
242        let error_json = json!({
243            "code": -32000,
244            "message": "Execution reverted"
245        });
246        let err: SimulateError = serde_json::from_value(error_json).unwrap();
247        assert_eq!(err.data, None);
248    }
249
250    #[test]
251    #[cfg(feature = "serde")]
252    fn test_deserialize_simulate_error_with_data() {
253        let error_json = json!({
254            "code": -32000,
255            "message": "Execution reverted",
256            "data": "0xcabedea8"
257        });
258        let err: SimulateError = serde_json::from_value(error_json).unwrap();
259        assert_eq!(err.data, Some(bytes!("cabedea8")));
260    }
261
262    #[test]
263    #[cfg(feature = "serde")]
264    fn test_eth_simulate_v1_account_not_precompile() {
265        let request_json = json!({
266            "jsonrpc": "2.0",
267            "id": 1,
268            "method": "eth_simulateV1",
269            "params": [{
270                "blockStateCalls": [
271                    {
272                        "blockOverrides": {},
273                        "stateOverrides": {
274                            "0xc000000000000000000000000000000000000000": {
275                                "nonce": "0x5"
276                            }
277                        },
278                        "calls": []
279                    },
280                    {
281                        "blockOverrides": {},
282                        "stateOverrides": {
283                            "0xc000000000000000000000000000000000000000": {
284                                "code": "0x600035600055"
285                            }
286                        },
287                        "calls": [
288                            {
289                                "from": "0xc000000000000000000000000000000000000000",
290                                "to": "0xc000000000000000000000000000000000000000",
291                                "nonce": "0x0"
292                            },
293                            {
294                                "from": "0xc100000000000000000000000000000000000000",
295                                "to": "0xc100000000000000000000000000000000000000",
296                                "nonce": "0x5"
297                            }
298                        ]
299                    }
300                ],
301                "traceTransfers": false,
302                "validation": true,
303                "returnFullTransactions": false
304            }, "latest"]
305        });
306
307        let sim_opts: SimulatePayload =
308            serde_json::from_value(request_json["params"][0].clone()).unwrap();
309
310        let address_1: Address = "0xc000000000000000000000000000000000000000".parse().unwrap();
311        let address_2: Address = "0xc100000000000000000000000000000000000000".parse().unwrap();
312
313        assert!(sim_opts.validation);
314        assert_eq!(sim_opts.block_state_calls.len(), 2);
315
316        let block_state_call_1 = &sim_opts.block_state_calls[0];
317        assert!(block_state_call_1.state_overrides.as_ref().unwrap().contains_key(&address_1));
318        assert_eq!(
319            block_state_call_1
320                .state_overrides
321                .as_ref()
322                .unwrap()
323                .get(&address_1)
324                .unwrap()
325                .nonce
326                .unwrap(),
327            5
328        );
329
330        let block_state_call_2 = &sim_opts.block_state_calls[1];
331        assert!(block_state_call_2.state_overrides.as_ref().unwrap().contains_key(&address_1));
332
333        assert_eq!(block_state_call_2.calls.len(), 2);
334        assert_eq!(block_state_call_2.calls[0].from.unwrap(), address_1);
335        assert_eq!(block_state_call_2.calls[0].to.unwrap(), TxKind::Call(address_1));
336        assert_eq!(block_state_call_2.calls[0].nonce.unwrap(), 0);
337        assert_eq!(block_state_call_2.calls[1].from.unwrap(), address_2);
338        assert_eq!(block_state_call_2.calls[1].to.unwrap(), TxKind::Call(address_2));
339        assert_eq!(block_state_call_2.calls[1].nonce.unwrap(), 5);
340    }
341
342    #[test]
343    fn test_simulate_error_codes() {
344        assert_eq!(SimulateError::EXECUTION_REVERTED_CODE, EthRpcErrorCode::ExecutionError.code());
345        assert_eq!(SimulateError::VM_EXECUTION_ERROR_CODE, -32015);
346        assert_eq!(SimulateError::invalid_params().code, SimulateError::INVALID_PARAMS_ERROR_CODE);
347    }
348}