foundry_cheatcodes/
script.rs

1//! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes.
2
3use crate::{Cheatcode, CheatsCtxt, Result, Vm::*};
4use alloy_primitives::{Address, PrimitiveSignature, B256, U256};
5use alloy_rpc_types::Authorization;
6use alloy_signer::SignerSync;
7use alloy_signer_local::PrivateKeySigner;
8use alloy_sol_types::SolValue;
9use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner};
10use parking_lot::Mutex;
11use revm::primitives::{Bytecode, SignedAuthorization};
12use std::sync::Arc;
13
14impl Cheatcode for broadcast_0Call {
15    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
16        let Self {} = self;
17        broadcast(ccx, None, true)
18    }
19}
20
21impl Cheatcode for broadcast_1Call {
22    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
23        let Self { signer } = self;
24        broadcast(ccx, Some(signer), true)
25    }
26}
27
28impl Cheatcode for broadcast_2Call {
29    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
30        let Self { privateKey } = self;
31        broadcast_key(ccx, privateKey, true)
32    }
33}
34
35impl Cheatcode for attachDelegationCall {
36    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
37        let Self { signedDelegation } = self;
38        let SignedDelegation { v, r, s, nonce, implementation } = signedDelegation;
39
40        let auth = Authorization {
41            address: *implementation,
42            nonce: *nonce,
43            chain_id: U256::from(ccx.ecx.env.cfg.chain_id),
44        };
45        let signed_auth = SignedAuthorization::new_unchecked(
46            auth,
47            *v,
48            U256::from_be_bytes(r.0),
49            U256::from_be_bytes(s.0),
50        );
51        write_delegation(ccx, signed_auth.clone())?;
52        ccx.state.active_delegation = Some(signed_auth);
53        Ok(Default::default())
54    }
55}
56
57impl Cheatcode for signDelegationCall {
58    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
59        let Self { implementation, privateKey } = self;
60        let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?;
61        let authority = signer.address();
62        let (auth, nonce) = create_auth(ccx, *implementation, authority)?;
63        let sig = signer.sign_hash_sync(&auth.signature_hash())?;
64        Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode())
65    }
66}
67
68impl Cheatcode for signAndAttachDelegationCall {
69    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
70        let Self { implementation, privateKey } = self;
71        let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?;
72        let authority = signer.address();
73        let (auth, nonce) = create_auth(ccx, *implementation, authority)?;
74        let sig = signer.sign_hash_sync(&auth.signature_hash())?;
75        let signed_auth = sig_to_auth(sig, auth);
76        write_delegation(ccx, signed_auth.clone())?;
77        ccx.state.active_delegation = Some(signed_auth);
78        Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode())
79    }
80}
81
82fn create_auth(
83    ccx: &mut CheatsCtxt,
84    implementation: Address,
85    authority: Address,
86) -> Result<(Authorization, u64)> {
87    let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?;
88    let nonce = authority_acc.data.info.nonce;
89    Ok((
90        Authorization {
91            address: implementation,
92            nonce,
93            chain_id: U256::from(ccx.ecx.env.cfg.chain_id),
94        },
95        nonce,
96    ))
97}
98
99fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> {
100    let authority = auth.recover_authority().map_err(|e| format!("{e}"))?;
101    let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?;
102    if authority_acc.data.info.nonce != auth.nonce {
103        return Err("invalid nonce".into());
104    }
105    authority_acc.data.info.nonce += 1;
106    let bytecode = Bytecode::new_eip7702(*auth.address());
107    ccx.ecx.journaled_state.set_code(authority, bytecode);
108    Ok(())
109}
110
111fn sig_to_delegation(
112    sig: PrimitiveSignature,
113    nonce: u64,
114    implementation: Address,
115) -> SignedDelegation {
116    SignedDelegation {
117        v: sig.v() as u8,
118        r: sig.r().into(),
119        s: sig.s().into(),
120        nonce,
121        implementation,
122    }
123}
124
125fn sig_to_auth(sig: PrimitiveSignature, auth: Authorization) -> SignedAuthorization {
126    SignedAuthorization::new_unchecked(auth, sig.v() as u8, sig.r(), sig.s())
127}
128
129impl Cheatcode for startBroadcast_0Call {
130    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
131        let Self {} = self;
132        broadcast(ccx, None, false)
133    }
134}
135
136impl Cheatcode for startBroadcast_1Call {
137    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
138        let Self { signer } = self;
139        broadcast(ccx, Some(signer), false)
140    }
141}
142
143impl Cheatcode for startBroadcast_2Call {
144    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
145        let Self { privateKey } = self;
146        broadcast_key(ccx, privateKey, false)
147    }
148}
149
150impl Cheatcode for stopBroadcastCall {
151    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
152        let Self {} = self;
153        let Some(broadcast) = ccx.state.broadcast.take() else {
154            bail!("no broadcast in progress to stop");
155        };
156        debug!(target: "cheatcodes", ?broadcast, "stopped");
157        Ok(Default::default())
158    }
159}
160
161impl Cheatcode for getWalletsCall {
162    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
163        let wallets = ccx.state.wallets().signers().unwrap_or_default();
164        Ok(wallets.abi_encode())
165    }
166}
167
168#[derive(Clone, Debug, Default)]
169pub struct Broadcast {
170    /// Address of the transaction origin
171    pub new_origin: Address,
172    /// Original caller
173    pub original_caller: Address,
174    /// Original `tx.origin`
175    pub original_origin: Address,
176    /// Depth of the broadcast
177    pub depth: u64,
178    /// Whether the prank stops by itself after the next call
179    pub single_call: bool,
180}
181
182/// Contains context for wallet management.
183#[derive(Debug)]
184pub struct WalletsInner {
185    /// All signers in scope of the script.
186    pub multi_wallet: MultiWallet,
187    /// Optional signer provided as `--sender` flag.
188    pub provided_sender: Option<Address>,
189}
190
191/// Clonable wrapper around [`WalletsInner`].
192#[derive(Debug, Clone)]
193pub struct Wallets {
194    /// Inner data.
195    pub inner: Arc<Mutex<WalletsInner>>,
196}
197
198impl Wallets {
199    #[allow(missing_docs)]
200    pub fn new(multi_wallet: MultiWallet, provided_sender: Option<Address>) -> Self {
201        Self { inner: Arc::new(Mutex::new(WalletsInner { multi_wallet, provided_sender })) }
202    }
203
204    /// Consumes [Wallets] and returns [MultiWallet].
205    ///
206    /// Panics if [Wallets] is still in use.
207    pub fn into_multi_wallet(self) -> MultiWallet {
208        Arc::into_inner(self.inner)
209            .map(|m| m.into_inner().multi_wallet)
210            .unwrap_or_else(|| panic!("not all instances were dropped"))
211    }
212
213    /// Locks inner Mutex and adds a signer to the [MultiWallet].
214    pub fn add_private_key(&self, private_key: &B256) -> Result<()> {
215        self.add_local_signer(PrivateKeySigner::from_bytes(private_key)?);
216        Ok(())
217    }
218
219    /// Locks inner Mutex and adds a signer to the [MultiWallet].
220    pub fn add_local_signer(&self, wallet: PrivateKeySigner) {
221        self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet));
222    }
223
224    /// Locks inner Mutex and returns all signer addresses in the [MultiWallet].
225    pub fn signers(&self) -> Result<Vec<Address>> {
226        Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect())
227    }
228
229    /// Number of signers in the [MultiWallet].
230    pub fn len(&self) -> usize {
231        let mut inner = self.inner.lock();
232        let signers = inner.multi_wallet.signers();
233        if signers.is_err() {
234            return 0;
235        }
236        signers.unwrap().len()
237    }
238
239    /// Whether the [MultiWallet] is empty.
240    pub fn is_empty(&self) -> bool {
241        self.len() == 0
242    }
243}
244
245/// Sets up broadcasting from a script using `new_origin` as the sender.
246fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bool) -> Result {
247    ensure!(
248        ccx.state.prank.is_none(),
249        "you have an active prank; broadcasting and pranks are not compatible"
250    );
251    ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already");
252
253    let mut new_origin = new_origin.cloned();
254
255    if new_origin.is_none() {
256        let mut wallets = ccx.state.wallets().inner.lock();
257        if let Some(provided_sender) = wallets.provided_sender {
258            new_origin = Some(provided_sender);
259        } else {
260            let signers = wallets.multi_wallet.signers()?;
261            if signers.len() == 1 {
262                let address = signers.keys().next().unwrap();
263                new_origin = Some(*address);
264            }
265        }
266    }
267
268    let broadcast = Broadcast {
269        new_origin: new_origin.unwrap_or(ccx.ecx.env.tx.caller),
270        original_caller: ccx.caller,
271        original_origin: ccx.ecx.env.tx.caller,
272        depth: ccx.ecx.journaled_state.depth(),
273        single_call,
274    };
275    debug!(target: "cheatcodes", ?broadcast, "started");
276    ccx.state.broadcast = Some(broadcast);
277    Ok(Default::default())
278}
279
280/// Sets up broadcasting from a script with the sender derived from `private_key`.
281/// Adds this private key to `state`'s `wallets` vector to later be used for signing
282/// if broadcast is successful.
283fn broadcast_key(ccx: &mut CheatsCtxt, private_key: &U256, single_call: bool) -> Result {
284    let wallet = super::crypto::parse_wallet(private_key)?;
285    let new_origin = wallet.address();
286
287    let result = broadcast(ccx, Some(&new_origin), single_call);
288    if result.is_ok() {
289        let wallets = ccx.state.wallets();
290        wallets.add_local_signer(wallet);
291    }
292    result
293}