1use 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 pub new_origin: Address,
172 pub original_caller: Address,
174 pub original_origin: Address,
176 pub depth: u64,
178 pub single_call: bool,
180}
181
182#[derive(Debug)]
184pub struct WalletsInner {
185 pub multi_wallet: MultiWallet,
187 pub provided_sender: Option<Address>,
189}
190
191#[derive(Debug, Clone)]
193pub struct Wallets {
194 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 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 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 pub fn add_local_signer(&self, wallet: PrivateKeySigner) {
221 self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet));
222 }
223
224 pub fn signers(&self) -> Result<Vec<Address>> {
226 Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect())
227 }
228
229 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 pub fn is_empty(&self) -> bool {
241 self.len() == 0
242 }
243}
244
245fn 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
280fn 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}