1use crate::{
2 build::{LinkedBuildData, ScriptPredeployLibraries},
3 execute::LinkedState,
4 simulate::PreSimulationState,
5};
6use alloy_network::TransactionBuilder;
7use alloy_primitives::{Address, keccak256};
8use eyre::Result;
9use foundry_common::{LIBRARY_DEPLOYER, matches_contract_creation, shell};
10use foundry_compilers::ArtifactId;
11use foundry_evm::{constants::CHEATCODE_ADDRESS, core::evm::FoundryEvmNetwork};
12use std::collections::BTreeSet;
13
14struct EligibleTransactions {
15 required: BTreeSet<ArtifactId>,
16 artifacts: Vec<ArtifactId>,
17 rpc: String,
18 baseline_count: usize,
19}
20
21const RERUN_UNSAFE_CHEATCODE_SIGNATURES: &[&str] = &[
22 "setEnv(string,string)",
23 "sign(bytes32)",
24 "sign(address,bytes32)",
25 "signCompact(bytes32)",
26 "signCompact(address,bytes32)",
27 "rpc(string,string)",
28 "rpc(string,string,string)",
29 "rpcJson(string,string)",
30 "rpcJson(string,string,string)",
31 "sleep(uint256)",
32 "prompt(string)",
33 "promptSecret(string)",
34 "promptSecretUint(string)",
35 "promptAddress(string)",
36 "promptUint(string)",
37 "dumpState(string)",
38 "createFork(string)",
39 "createFork(string,uint256)",
40 "createFork(string,bytes32)",
41 "createSelectFork(string)",
42 "createSelectFork(string,uint256)",
43 "createSelectFork(string,bytes32)",
44 "rollFork(uint256)",
45 "rollFork(bytes32)",
46 "rollFork(uint256,uint256)",
47 "rollFork(uint256,bytes32)",
48 "selectFork(uint256)",
49 "transact(bytes32)",
50 "transact(uint256,bytes32)",
51];
52
53pub(crate) fn rerun_unsafe_cheatcode_selectors() -> Vec<[u8; 4]> {
54 RERUN_UNSAFE_CHEATCODE_SIGNATURES
55 .iter()
56 .map(|signature| {
57 let hash = keccak256(signature.as_bytes());
58 [hash[0], hash[1], hash[2], hash[3]]
59 })
60 .collect()
61}
62
63impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
64 pub async fn optimize_library_deployments(self) -> Result<Self> {
67 let Ok(Some(EligibleTransactions { required, artifacts, rpc, baseline_count })) =
68 self.eligible()
69 else {
70 return Ok(self);
71 };
72 let onchain = match &self.build_data.predeploy_libraries {
73 ScriptPredeployLibraries::Default { onchain, .. }
74 | ScriptPredeployLibraries::Create2 { onchain, .. } => onchain,
75 };
76 if required.len() == onchain.len() {
77 return Ok(self);
78 }
79
80 let Ok(libraries) = self.script_config.config.libraries_with_remappings() else {
81 return Ok(self);
82 };
83 let linker = self.build_data.build_data.get_linker();
84 let linked = match &self.build_data.predeploy_libraries {
85 ScriptPredeployLibraries::Default { .. } => linker
86 .link_with_partition(
87 libraries,
88 self.script_config.evm_opts.sender,
89 self.script_config.sender_nonce,
90 LIBRARY_DEPLOYER,
91 &required,
92 &self.build_data.build_data.target,
93 )
94 .map(|(output, local)| {
95 let onchain = onchain_libraries(&output, &local);
96 (output.output, ScriptPredeployLibraries::Default { onchain, local })
97 }),
98 ScriptPredeployLibraries::Create2 { salt, .. } => linker
99 .link_with_create2_partition(
100 libraries,
101 self.script_config.evm_opts.create2_deployer,
102 *salt,
103 LIBRARY_DEPLOYER,
104 &required,
105 &self.build_data.build_data.target,
106 )
107 .map(|(output, local)| {
108 let onchain = onchain_libraries(&output, &local);
109 (
110 output.output,
111 ScriptPredeployLibraries::Create2 { onchain, salt: *salt, local },
112 )
113 }),
114 };
115 let Ok((output, predeploy_libraries)) = linked else { return Ok(self) };
116 let Ok(build_data) = LinkedBuildData::new(
117 output.libraries,
118 predeploy_libraries,
119 self.build_data.build_data.clone(),
120 ) else {
121 return Ok(self);
122 };
123 let candidate = LinkedState {
124 args: self.args.clone(),
125 script_config: self.script_config.clone(),
126 script_wallets: self.script_wallets.clone(),
127 browser_wallet: self.browser_wallet.clone(),
128 build_data,
129 }
130 .prepare_execution()
131 .await;
132 let Ok(candidate) = candidate else { return Ok(self) };
133 let candidate = candidate.execute_restricted().await;
134 let Ok(candidate) = candidate else { return Ok(self) };
135 if !candidate.execution_result.success {
136 return Ok(self);
137 }
138 let candidate = candidate.prepare_simulation_silent().await;
139 let Ok(candidate) = candidate else { return Ok(self) };
140 if self.execution_result.returned != candidate.execution_result.returned
141 || self.execution_result.logs != candidate.execution_result.logs
142 || !self.equivalent_candidate(&candidate, &artifacts, &rpc, baseline_count)
143 {
144 return Ok(self);
145 }
146 Ok(candidate)
147 }
148
149 fn eligible(&self) -> Result<Option<EligibleTransactions>> {
150 if !self.execution_result.success
151 || self.args.skip_simulation
152 || self.args.debug
153 || self.args.dump.is_some()
154 || self.args.batch
155 || self.args.slow
156 || self.script_config.config.ffi
157 || self.script_config.config.live_logs
158 || shell::is_json()
159 || shell::verbosity() > 3
160 || self.script_config.evm_opts.env.gas_price.is_some()
161 || self.execution_artifacts.rpc_data.missing_rpc
162 || self.execution_artifacts.rpc_data.is_multi_chain()
163 || self.script_config.evm_opts.sender == LIBRARY_DEPLOYER
164 || self.script_config.config.fs_permissions.permissions.iter().any(|permission| {
165 matches!(
166 permission.access,
167 foundry_config::fs_permissions::FsAccessPermission::Write
168 | foundry_config::fs_permissions::FsAccessPermission::ReadWrite
169 )
170 })
171 || self.used_rerun_unsafe_cheatcode()
172 {
173 return Ok(None);
174 }
175 let Some(rpc) = self.script_config.evm_opts.fork_url.clone() else { return Ok(None) };
176 if self.script_config.resolved_fork()?.is_none()
177 || self.execution_artifacts.rpc_data.total_rpcs.len() != 1
178 || !self.execution_artifacts.rpc_data.total_rpcs.contains(&rpc)
179 {
180 return Ok(None);
181 }
182 let onchain = match &self.build_data.predeploy_libraries {
183 ScriptPredeployLibraries::Default { onchain, .. }
184 | ScriptPredeployLibraries::Create2 { onchain, .. } => onchain,
185 };
186 if onchain.is_empty() {
187 return Ok(None);
188 }
189 let Some(transactions) = &self.execution_result.transactions else { return Ok(None) };
190 if transactions.len() <= onchain.len()
191 || transactions.iter().any(|tx| {
192 !tx.transaction.is_unsigned()
193 || tx.transaction.authorization_list().is_some_and(|list| !list.is_empty())
194 })
195 {
196 return Ok(None);
197 }
198 for (index, (tx, library)) in transactions.iter().zip(onchain).enumerate() {
199 let input = tx.transaction.input().map(|input| input.as_ref()).unwrap_or_default();
200 let deployment_matches = match &self.build_data.predeploy_libraries {
201 ScriptPredeployLibraries::Default { .. } => {
202 tx.transaction.to().is_none() && input == library.bytecode.as_ref()
203 }
204 ScriptPredeployLibraries::Create2 { salt, .. } => {
205 tx.transaction.to() == Some(self.script_config.evm_opts.create2_deployer)
206 && input.get(..32) == Some(salt.as_slice())
207 && input.get(32..) == Some(library.bytecode.as_ref())
208 }
209 };
210 let exact = deployment_matches
211 && tx.rpc.as_ref() == Some(&rpc)
212 && tx.transaction.from() == Some(self.script_config.evm_opts.sender)
213 && tx.transaction.nonce() == Some(self.script_config.sender_nonce + index as u64);
214 if !exact {
215 return Ok(None);
216 }
217 }
218
219 let library_ids = onchain.iter().map(|library| library.id.clone()).collect::<BTreeSet<_>>();
220 let linker = self.build_data.build_data.get_linker();
221 let mut required = BTreeSet::new();
222 let mut artifacts = Vec::new();
223 for tx in transactions.iter().skip(onchain.len()) {
224 if tx.rpc.as_ref() != Some(&rpc)
225 || tx.transaction.from() != Some(self.script_config.evm_opts.sender)
226 || tx.transaction.to().is_some()
227 {
228 return Ok(None);
229 }
230 let Some(input) = tx.transaction.input() else { return Ok(None) };
231 let matches = self
232 .build_data
233 .known_contracts
234 .iter()
235 .filter(|(_, contract)| matches_contract_creation(contract, input))
236 .map(|(id, _)| id)
237 .collect::<Vec<_>>();
238 let [artifact] = matches.as_slice() else { return Ok(None) };
239 artifacts.push((*artifact).clone());
240 required.extend(
241 linker.dependencies(artifact)?.into_iter().filter(|id| library_ids.contains(id)),
242 );
243 }
244 Ok(Some(EligibleTransactions { required, artifacts, rpc, baseline_count: onchain.len() }))
245 }
246
247 fn equivalent_candidate(
248 &self,
249 candidate: &Self,
250 artifacts: &[ArtifactId],
251 rpc: &str,
252 baseline_count: usize,
253 ) -> bool {
254 let Some(baseline) = self.execution_result.transactions.as_ref() else { return false };
255 let Some(candidate_txs) = candidate.execution_result.transactions.as_ref() else {
256 return false;
257 };
258 let candidate_count = candidate.build_data.predeploy_libraries.libraries_count();
259 let baseline = baseline.iter().skip(baseline_count).collect::<Vec<_>>();
260 let candidate_txs = candidate_txs.iter().skip(candidate_count).collect::<Vec<_>>();
261 if baseline.len() != artifacts.len() || candidate_txs.len() != artifacts.len() {
262 return false;
263 }
264 let mut remaps = Vec::new();
265 let baseline_libraries = match &self.build_data.predeploy_libraries {
266 ScriptPredeployLibraries::Default { onchain, .. }
267 | ScriptPredeployLibraries::Create2 { onchain, .. } => onchain,
268 };
269 let candidate_libraries = match &candidate.build_data.predeploy_libraries {
270 ScriptPredeployLibraries::Default { onchain, .. }
271 | ScriptPredeployLibraries::Create2 { onchain, .. } => onchain,
272 };
273 for library in baseline_libraries {
274 if let Some(other) = candidate_libraries.iter().find(|other| other.id == library.id) {
275 remaps.push((library.address, other.address));
276 }
277 }
278 for ((baseline, candidate_tx), artifact) in
279 baseline.iter().zip(&candidate_txs).zip(artifacts)
280 {
281 let (Some(mut old), Some(new)) = (
282 baseline.transaction.clone().as_unsigned_mut().cloned(),
283 candidate_tx.transaction.clone().as_unsigned_mut().cloned(),
284 ) else {
285 return false;
286 };
287 if baseline.rpc.as_deref() != Some(rpc)
288 || candidate_tx.rpc.as_deref() != Some(rpc)
289 || old.from() != new.from()
290 || old.to().is_some()
291 || new.to().is_some()
292 {
293 return false;
294 }
295 let (Some(old_nonce), Some(new_nonce)) = (old.nonce(), new.nonce()) else {
296 return false;
297 };
298 if old_nonce.checked_sub(baseline_count as u64)
299 != new_nonce.checked_sub(candidate_count as u64)
300 {
301 return false;
302 }
303 let mut input = old.input().cloned().unwrap_or_default().to_vec();
304 for (from, to) in &remaps {
305 replace_addresses(&mut input, *from, *to);
306 }
307 old.set_nonce(new_nonce);
308 old.set_input(input);
309 let (Ok(old_value), Ok(new_value)) =
310 (serde_json::to_value(&old), serde_json::to_value(&new))
311 else {
312 return false;
313 };
314 if old_value != new_value
315 || !candidate.build_data.known_contracts.get(artifact).is_some_and(|contract| {
316 new.input().is_some_and(|input| matches_contract_creation(contract, input))
317 })
318 {
319 return false;
320 }
321 remaps.push((
322 old.from().unwrap().create(old_nonce),
323 new.from().unwrap().create(new_nonce),
324 ));
325 }
326 true
327 }
328
329 fn used_rerun_unsafe_cheatcode(&self) -> bool {
330 let selectors = rerun_unsafe_cheatcode_selectors();
331 self.execution_result.traces.iter().any(|(_, traces)| {
332 traces.nodes().iter().any(|node| {
333 node.trace.address == CHEATCODE_ADDRESS
334 && node.trace.data.get(..4).is_some_and(|selector| {
335 selectors.iter().any(|blocked| blocked.as_slice() == selector)
336 })
337 })
338 })
339 }
340}
341
342fn onchain_libraries(
343 output: &foundry_linking::DetailedLinkOutput,
344 local: &[foundry_linking::LinkedLibrary],
345) -> Vec<foundry_linking::LinkedLibrary> {
346 output
347 .linked_libraries
348 .iter()
349 .filter(|library| !local.iter().any(|local| local.id == library.id))
350 .cloned()
351 .collect()
352}
353
354fn replace_addresses(input: &mut [u8], from: Address, to: Address) {
355 let mut offset = 0;
356 while let Some(index) =
357 input[offset..].windows(Address::len_bytes()).position(|window| window == from.as_slice())
358 {
359 let start = offset + index;
360 input[start..start + Address::len_bytes()].copy_from_slice(to.as_slice());
361 offset = start + Address::len_bytes();
362 }
363}