1use std::{any::Any, fmt::Debug, sync::Arc};
2
3use alloy_consensus::BlobTransactionSidecar;
4use alloy_primitives::TxKind;
5use alloy_rpc_types::{SignedAuthorization, TransactionInput, TransactionRequest};
6use revm::interpreter::{CallInputs, Interpreter};
7
8use crate::{
9 BroadcastableTransaction, BroadcastableTransactions, CheatcodesExecutor, CheatsConfig,
10 CheatsCtxt, DynCheatcode, Result,
11 inspector::{CommonCreateInput, Ecx, check_if_fixed_gas_limit},
12 script::Broadcast,
13};
14
15pub trait CheatcodeInspectorStrategyContext: Debug + Send + Sync + Any {
17 fn new_cloned(&self) -> Box<dyn CheatcodeInspectorStrategyContext>;
19 fn as_any_ref(&self) -> &dyn Any;
21 fn as_any_mut(&mut self) -> &mut dyn Any;
23}
24
25impl Clone for Box<dyn CheatcodeInspectorStrategyContext> {
26 fn clone(&self) -> Self {
27 self.new_cloned()
28 }
29}
30
31impl CheatcodeInspectorStrategyContext for () {
33 fn new_cloned(&self) -> Box<dyn CheatcodeInspectorStrategyContext> {
34 Box::new(())
35 }
36
37 fn as_any_mut(&mut self) -> &mut dyn Any {
38 self
39 }
40
41 fn as_any_ref(&self) -> &dyn Any {
42 self
43 }
44}
45
46pub trait CheatcodeInspectorStrategyRunner:
48 Debug + Send + Sync + CheatcodeInspectorStrategyExt
49{
50 fn apply_full(
52 &self,
53 cheatcode: &dyn DynCheatcode,
54 ccx: &mut CheatsCtxt,
55 executor: &mut dyn CheatcodesExecutor,
56 ) -> Result {
57 cheatcode.dyn_apply(ccx, executor)
58 }
59
60 fn base_contract_deployed(&self, _ctx: &mut dyn CheatcodeInspectorStrategyContext) {}
62
63 fn record_broadcastable_create_transactions(
65 &self,
66 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
67 config: Arc<CheatsConfig>,
68 input: &dyn CommonCreateInput,
69 ecx: Ecx,
70 broadcast: &Broadcast,
71 broadcastable_transactions: &mut BroadcastableTransactions,
72 );
73
74 #[allow(clippy::too_many_arguments)]
76 fn record_broadcastable_call_transactions(
77 &self,
78 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
79 _config: Arc<CheatsConfig>,
80 input: &CallInputs,
81 ecx: Ecx,
82 broadcast: &Broadcast,
83 broadcastable_transactions: &mut BroadcastableTransactions,
84 active_delegations: Vec<SignedAuthorization>,
85 active_blob_sidecar: Option<BlobTransactionSidecar>,
86 );
87
88 fn pre_initialize_interp(
90 &self,
91 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
92 _interpreter: &mut Interpreter,
93 _ecx: Ecx,
94 ) {
95 }
96
97 fn post_initialize_interp(
99 &self,
100 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
101 _interpreter: &mut Interpreter,
102 _ecx: Ecx,
103 ) {
104 }
105
106 fn pre_step_end(
110 &self,
111 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
112 _interpreter: &mut Interpreter,
113 _ecx: Ecx,
114 ) -> bool {
115 false
116 }
117}
118
119#[derive(Debug, Default, Clone)]
121pub struct EvmCheatcodeInspectorStrategyRunner;
122
123impl CheatcodeInspectorStrategyRunner for EvmCheatcodeInspectorStrategyRunner {
124 fn record_broadcastable_create_transactions(
125 &self,
126 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
127 _config: Arc<CheatsConfig>,
128 input: &dyn CommonCreateInput,
129 ecx: Ecx,
130 broadcast: &Broadcast,
131 broadcastable_transactions: &mut BroadcastableTransactions,
132 ) {
133 let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit());
134
135 let to = None;
136 let nonce: u64 = ecx.journaled_state.state()[&broadcast.new_origin].info.nonce;
137 let call_init_code = input.init_code();
139 let rpc = ecx.journaled_state.database.active_fork_url();
140
141 broadcastable_transactions.push_back(BroadcastableTransaction {
142 rpc,
143 transaction: TransactionRequest {
144 from: Some(broadcast.new_origin),
145 to,
146 value: Some(input.value()),
147 input: TransactionInput::new(call_init_code),
148 nonce: Some(nonce),
149 gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None },
150 ..Default::default()
151 }
152 .into(),
153 });
154 }
155
156 fn record_broadcastable_call_transactions(
157 &self,
158 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
159 _config: Arc<CheatsConfig>,
160 inputs: &CallInputs,
161 ecx: Ecx,
162 broadcast: &Broadcast,
163 broadcastable_transactions: &mut BroadcastableTransactions,
164 active_delegations: Vec<SignedAuthorization>,
165 mut active_blob_sidecar: Option<BlobTransactionSidecar>,
166 ) {
167 let input = TransactionInput::new(inputs.input.bytes(ecx));
168 let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, inputs.gas_limit);
169
170 let account = ecx.journaled_state.state().get_mut(&broadcast.new_origin).unwrap();
171 let nonce = account.info.nonce;
172
173 let mut tx_req = TransactionRequest {
174 from: Some(broadcast.new_origin),
175 to: Some(TxKind::from(Some(inputs.target_address))),
176 value: inputs.transfer_value(),
177 input,
178 nonce: Some(nonce),
179 chain_id: Some(ecx.cfg.chain_id),
180 gas: if is_fixed_gas_limit { Some(inputs.gas_limit) } else { None },
181 ..Default::default()
182 };
183
184 if let Some(blob_sidecar) = active_blob_sidecar.take()
186 && active_delegations.is_empty()
187 {
188 use alloy_network::TransactionBuilder4844;
189 tx_req.set_blob_sidecar(blob_sidecar);
191 }
192
193 if !active_delegations.is_empty() {
194 for auth in &active_delegations {
195 let Ok(authority) = auth.recover_authority() else {
196 continue;
197 };
198 if authority == broadcast.new_origin {
199 account.info.nonce += 1;
202 }
203 }
204 tx_req.authorization_list = Some(active_delegations);
205 }
206
207 broadcastable_transactions.push_back(BroadcastableTransaction {
208 rpc: ecx.journaled_state.database.active_fork_url(),
209 transaction: tx_req.into(),
210 });
211 debug!(target: "cheatcodes", tx=?broadcastable_transactions.back().unwrap(), "broadcastable call");
212 }
213}
214
215impl CheatcodeInspectorStrategyExt for EvmCheatcodeInspectorStrategyRunner {}
216
217#[derive(Debug)]
219pub struct CheatcodeInspectorStrategy {
220 pub runner: &'static dyn CheatcodeInspectorStrategyRunner,
222 pub context: Box<dyn CheatcodeInspectorStrategyContext>,
224}
225
226impl CheatcodeInspectorStrategy {
227 pub fn new_evm() -> Self {
229 Self { runner: &EvmCheatcodeInspectorStrategyRunner, context: Box::new(()) }
230 }
231}
232
233impl Clone for CheatcodeInspectorStrategy {
234 fn clone(&self) -> Self {
235 Self { runner: self.runner, context: self.context.new_cloned() }
236 }
237}
238
239pub trait CheatcodeInspectorStrategyExt {
241 fn is_pvm_enabled(&self, _state: &mut crate::Cheatcodes) -> bool {
242 false
243 }
244
245 fn revive_try_create(
246 &self,
247 _state: &mut crate::Cheatcodes,
248 _ecx: Ecx,
249 _input: &dyn CommonCreateInput,
250 _executor: &mut dyn CheatcodesExecutor,
251 ) -> Option<revm::interpreter::CreateOutcome> {
252 None
253 }
254
255 fn revive_try_call(
256 &self,
257 _state: &mut crate::Cheatcodes,
258 _ecx: Ecx,
259 _input: &CallInputs,
260 _executor: &mut dyn CheatcodesExecutor,
261 ) -> Option<revm::interpreter::CallOutcome> {
262 None
263 }
264
265 fn revive_call_end(&self, _state: &mut crate::Cheatcodes, _ecx: Ecx, _call: &CallInputs) {}
267
268 fn revive_remove_duplicate_account_access(&self, _state: &mut crate::Cheatcodes) {}
270
271 fn revive_record_create_address(
273 &self,
274 _state: &mut crate::Cheatcodes,
275 _outcome: &revm::interpreter::CreateOutcome,
276 ) {
277 }
278}
279
280pub type CheatcodesStrategy = CheatcodeInspectorStrategy;