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