alloy_rpc_types_eth/
simulate.rs

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