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/// The RPC accepts at most [`MAX_SIMULATE_BLOCKS`] blocks; this type and its builder methods do not
126/// enforce that limit.
127#[derive(Clone, Debug)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129#[cfg_attr(
130    feature = "serde",
131    serde(
132        rename_all = "camelCase",
133        bound(
134            deserialize = "TxReq: serde::Deserialize<'de>",
135            serialize = "TxReq: serde::Serialize"
136        )
137    )
138)]
139pub struct SimulatePayload<TxReq = TransactionRequest> {
140    /// Array of block state calls to be executed at specific, optional block/state.
141    #[cfg_attr(feature = "serde", serde(default))]
142    pub block_state_calls: Vec<SimBlock<TxReq>>,
143    /// Flag to determine whether to trace ERC20/ERC721 token transfers within transactions.
144    #[cfg_attr(feature = "serde", serde(default))]
145    pub trace_transfers: bool,
146    /// Flag to enable or disable validation of the transaction sequence in the blocks.
147    #[cfg_attr(feature = "serde", serde(default))]
148    pub validation: bool,
149    /// Flag to decide if full transactions should be returned instead of just their hashes.
150    #[cfg_attr(feature = "serde", serde(default))]
151    pub return_full_transactions: bool,
152}
153
154impl<TxReq> Default for SimulatePayload<TxReq> {
155    fn default() -> Self {
156        Self {
157            block_state_calls: Vec::new(),
158            trace_transfers: false,
159            validation: false,
160            return_full_transactions: false,
161        }
162    }
163}
164
165impl<TxReq> SimulatePayload<TxReq> {
166    /// Adds a block to the simulation payload.
167    pub fn extend(mut self, block: SimBlock<TxReq>) -> Self {
168        self.block_state_calls.push(block);
169        self
170    }
171
172    /// Adds multiple blocks to the simulation payload.
173    pub fn extend_blocks(mut self, blocks: impl IntoIterator<Item = SimBlock<TxReq>>) -> Self {
174        self.block_state_calls.extend(blocks);
175        self
176    }
177
178    /// Enables tracing of token transfers.
179    pub const fn with_trace_transfers(mut self) -> Self {
180        self.trace_transfers = true;
181        self
182    }
183
184    /// Enables validation of the transaction sequence.
185    pub const fn with_validation(mut self) -> Self {
186        self.validation = true;
187        self
188    }
189
190    /// Enables returning full transactions.
191    pub const fn with_full_transactions(mut self) -> Self {
192        self.return_full_transactions = true;
193        self
194    }
195}
196
197/// The error response returned by the `eth_simulateV1` method.
198#[derive(Clone, Debug)]
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
200#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
201pub struct SimulateError {
202    /// Error code.
203    ///
204    /// Known values:
205    /// - [`Self::EXECUTION_REVERTED_CODE`] for `Execution reverted`
206    /// - [`Self::VM_EXECUTION_ERROR_CODE`] for `VM execution error`
207    pub code: i32,
208    /// Message error
209    pub message: String,
210    /// Data for the error, e.g. revert reason.
211    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
212    pub data: Option<Bytes>,
213}
214
215impl SimulateError {
216    /// `Execution reverted` error code.
217    pub const EXECUTION_REVERTED_CODE: i32 = EthRpcErrorCode::ExecutionError.code();
218    /// `VM execution error` error code.
219    pub const VM_EXECUTION_ERROR_CODE: i32 = -32015;
220    /// `Invalid params` error code.
221    pub const INVALID_PARAMS_ERROR_CODE: i32 = -32602;
222
223    /// Creates a new invalid params error.
224    pub fn invalid_params() -> Self {
225        Self {
226            code: Self::INVALID_PARAMS_ERROR_CODE,
227            message: "invalid params".to_string(),
228            data: None,
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use alloy_primitives::{bytes, Address, TxKind};
237    #[cfg(feature = "serde")]
238    use serde_json::json;
239    use similar_asserts::assert_eq;
240
241    #[test]
242    #[cfg(feature = "serde")]
243    fn test_deserialize_simulate_error_no_data() {
244        let error_json = json!({
245            "code": -32000,
246            "message": "Execution reverted"
247        });
248        let err: SimulateError = serde_json::from_value(error_json).unwrap();
249        assert_eq!(err.data, None);
250    }
251
252    #[test]
253    #[cfg(feature = "serde")]
254    fn test_deserialize_simulate_error_with_data() {
255        let error_json = json!({
256            "code": -32000,
257            "message": "Execution reverted",
258            "data": "0xcabedea8"
259        });
260        let err: SimulateError = serde_json::from_value(error_json).unwrap();
261        assert_eq!(err.data, Some(bytes!("cabedea8")));
262    }
263
264    #[test]
265    #[cfg(feature = "serde")]
266    fn test_eth_simulate_v1_account_not_precompile() {
267        let request_json = json!({
268            "jsonrpc": "2.0",
269            "id": 1,
270            "method": "eth_simulateV1",
271            "params": [{
272                "blockStateCalls": [
273                    {
274                        "blockOverrides": {},
275                        "stateOverrides": {
276                            "0xc000000000000000000000000000000000000000": {
277                                "nonce": "0x5"
278                            }
279                        },
280                        "calls": []
281                    },
282                    {
283                        "blockOverrides": {},
284                        "stateOverrides": {
285                            "0xc000000000000000000000000000000000000000": {
286                                "code": "0x600035600055"
287                            }
288                        },
289                        "calls": [
290                            {
291                                "from": "0xc000000000000000000000000000000000000000",
292                                "to": "0xc000000000000000000000000000000000000000",
293                                "nonce": "0x0"
294                            },
295                            {
296                                "from": "0xc100000000000000000000000000000000000000",
297                                "to": "0xc100000000000000000000000000000000000000",
298                                "nonce": "0x5"
299                            }
300                        ]
301                    }
302                ],
303                "traceTransfers": false,
304                "validation": true,
305                "returnFullTransactions": false
306            }, "latest"]
307        });
308
309        let sim_opts: SimulatePayload =
310            serde_json::from_value(request_json["params"][0].clone()).unwrap();
311
312        let address_1: Address = "0xc000000000000000000000000000000000000000".parse().unwrap();
313        let address_2: Address = "0xc100000000000000000000000000000000000000".parse().unwrap();
314
315        assert!(sim_opts.validation);
316        assert_eq!(sim_opts.block_state_calls.len(), 2);
317
318        let block_state_call_1 = &sim_opts.block_state_calls[0];
319        assert!(block_state_call_1.state_overrides.as_ref().unwrap().contains_key(&address_1));
320        assert_eq!(
321            block_state_call_1
322                .state_overrides
323                .as_ref()
324                .unwrap()
325                .get(&address_1)
326                .unwrap()
327                .nonce
328                .unwrap(),
329            5
330        );
331
332        let block_state_call_2 = &sim_opts.block_state_calls[1];
333        assert!(block_state_call_2.state_overrides.as_ref().unwrap().contains_key(&address_1));
334
335        assert_eq!(block_state_call_2.calls.len(), 2);
336        assert_eq!(block_state_call_2.calls[0].from.unwrap(), address_1);
337        assert_eq!(block_state_call_2.calls[0].to.unwrap(), TxKind::Call(address_1));
338        assert_eq!(block_state_call_2.calls[0].nonce.unwrap(), 0);
339        assert_eq!(block_state_call_2.calls[1].from.unwrap(), address_2);
340        assert_eq!(block_state_call_2.calls[1].to.unwrap(), TxKind::Call(address_2));
341        assert_eq!(block_state_call_2.calls[1].nonce.unwrap(), 5);
342    }
343
344    #[test]
345    fn test_simulate_error_codes() {
346        assert_eq!(SimulateError::EXECUTION_REVERTED_CODE, EthRpcErrorCode::ExecutionError.code());
347        assert_eq!(SimulateError::VM_EXECUTION_ERROR_CODE, -32015);
348        assert_eq!(SimulateError::invalid_params().code, SimulateError::INVALID_PARAMS_ERROR_CODE);
349    }
350}