Skip to main content

foundry_cheatcodes/
strategy.rs

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
15/// Context for [CheatcodeInspectorStrategy].
16pub trait CheatcodeInspectorStrategyContext: Debug + Send + Sync + Any {
17    /// Clone the strategy context.
18    fn new_cloned(&self) -> Box<dyn CheatcodeInspectorStrategyContext>;
19    /// Alias as immutable reference of [Any].
20    fn as_any_ref(&self) -> &dyn Any;
21    /// Alias as mutable reference of [Any].
22    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
31/// Default strategy context object.
32impl 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
46/// Stateless strategy runner for [CheatcodeInspectorStrategy].
47pub trait CheatcodeInspectorStrategyRunner:
48    Debug + Send + Sync + CheatcodeInspectorStrategyExt
49{
50    /// Apply cheatcodes.
51    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    /// Called when the main test or script contract is deployed.
61    fn base_contract_deployed(&self, _ctx: &mut dyn CheatcodeInspectorStrategyContext) {}
62
63    /// Record broadcastable transaction during CREATE.
64    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    /// Record broadcastable transaction during CALL.
75    #[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    /// Hook for pre initialize_interp.
89    fn pre_initialize_interp(
90        &self,
91        _ctx: &mut dyn CheatcodeInspectorStrategyContext,
92        _interpreter: &mut Interpreter,
93        _ecx: Ecx,
94    ) {
95    }
96
97    /// Hook for post initialize_interp.
98    fn post_initialize_interp(
99        &self,
100        _ctx: &mut dyn CheatcodeInspectorStrategyContext,
101        _interpreter: &mut Interpreter,
102        _ecx: Ecx,
103    ) {
104    }
105
106    /// Hook for pre step_end.
107    ///
108    /// Used to override opcode behaviors. Returns true if handled.
109    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/// Implements [CheatcodeInspectorStrategyRunner] for EVM.
120#[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        //drop the mutable borrow of account
138        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        // Set active blob sidecar, if any.
185        if let Some(blob_sidecar) = active_blob_sidecar.take()
186            && active_delegations.is_empty()
187        {
188            use alloy_network::TransactionBuilder4844;
189            // Ensure blob and delegation are not set for the same tx.
190            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                    // Increment nonce of broadcasting account to reflect signed
200                    // authorization.
201                    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/// Defines the strategy for [super::Cheatcodes].
218#[derive(Debug)]
219pub struct CheatcodeInspectorStrategy {
220    /// Strategy runner.
221    pub runner: &'static dyn CheatcodeInspectorStrategyRunner,
222    /// Strategy context.
223    pub context: Box<dyn CheatcodeInspectorStrategyContext>,
224}
225
226impl CheatcodeInspectorStrategy {
227    /// Creates a new EVM strategy for the [super::Cheatcodes].
228    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
239/// Defined in revive-strategy
240pub 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    // Needed to sync from test contract callbacks
266    fn revive_call_end(&self, _state: &mut crate::Cheatcodes, _ecx: Ecx, _call: &CallInputs) {}
267
268    // Remove duplicate accesses in storage_recorder
269    fn revive_remove_duplicate_account_access(&self, _state: &mut crate::Cheatcodes) {}
270
271    // Record create address for skip_pvm_addresses tracking
272    fn revive_record_create_address(
273        &self,
274        _state: &mut crate::Cheatcodes,
275        _outcome: &revm::interpreter::CreateOutcome,
276    ) {
277    }
278}
279
280// Legacy type aliases for backward compatibility
281pub type CheatcodesStrategy = CheatcodeInspectorStrategy;