1use super::string::parse;
4use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*};
5use alloy_dyn_abi::DynSolType;
6use alloy_json_abi::ContractObject;
7use alloy_network::AnyTransactionReceipt;
8use alloy_primitives::{Bytes, U256, hex, map::Entry};
9use alloy_provider::network::ReceiptResponse;
10use alloy_sol_types::SolValue;
11use dialoguer::{Input, Password};
12use forge_script_sequence::{BroadcastReader, TransactionWithMetadata};
13use foundry_common::fs;
14use foundry_config::fs_permissions::FsAccessKind;
15use revm::{context::CreateScheme, interpreter::CreateInputs};
16use revm_inspectors::tracing::types::CallKind;
17use semver::Version;
18use std::{
19 io::{BufRead, BufReader},
20 path::{Path, PathBuf},
21 process::Command,
22 sync::mpsc,
23 thread,
24 time::{SystemTime, UNIX_EPOCH},
25};
26use walkdir::WalkDir;
27
28impl Cheatcode for existsCall {
29 fn apply(&self, state: &mut Cheatcodes) -> Result {
30 let Self { path } = self;
31 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
32 Ok(path.exists().abi_encode())
33 }
34}
35
36impl Cheatcode for fsMetadataCall {
37 fn apply(&self, state: &mut Cheatcodes) -> Result {
38 let Self { path } = self;
39 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
40
41 let metadata = path.metadata()?;
42
43 let [modified, accessed, created] =
45 [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| {
46 time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
47 });
48
49 Ok(FsMetadata {
50 isDir: metadata.is_dir(),
51 isSymlink: metadata.is_symlink(),
52 length: U256::from(metadata.len()),
53 readOnly: metadata.permissions().readonly(),
54 modified: U256::from(modified),
55 accessed: U256::from(accessed),
56 created: U256::from(created),
57 }
58 .abi_encode())
59 }
60}
61
62impl Cheatcode for isDirCall {
63 fn apply(&self, state: &mut Cheatcodes) -> Result {
64 let Self { path } = self;
65 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
66 Ok(path.is_dir().abi_encode())
67 }
68}
69
70impl Cheatcode for isFileCall {
71 fn apply(&self, state: &mut Cheatcodes) -> Result {
72 let Self { path } = self;
73 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
74 Ok(path.is_file().abi_encode())
75 }
76}
77
78impl Cheatcode for projectRootCall {
79 fn apply(&self, state: &mut Cheatcodes) -> Result {
80 let Self {} = self;
81 Ok(state.config.root.display().to_string().abi_encode())
82 }
83}
84
85impl Cheatcode for unixTimeCall {
86 fn apply(&self, _state: &mut Cheatcodes) -> Result {
87 let Self {} = self;
88 let difference = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?;
91 Ok(difference.as_millis().abi_encode())
92 }
93}
94
95impl Cheatcode for closeFileCall {
96 fn apply(&self, state: &mut Cheatcodes) -> Result {
97 let Self { path } = self;
98 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
99
100 state.test_context.opened_read_files.remove(&path);
101
102 Ok(Default::default())
103 }
104}
105
106impl Cheatcode for copyFileCall {
107 fn apply(&self, state: &mut Cheatcodes) -> Result {
108 let Self { from, to } = self;
109 let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?;
110 let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?;
111 state.config.ensure_not_foundry_toml(&to)?;
112
113 let n = fs::copy(from, to)?;
114 Ok(n.abi_encode())
115 }
116}
117
118impl Cheatcode for createDirCall {
119 fn apply(&self, state: &mut Cheatcodes) -> Result {
120 let Self { path, recursive } = self;
121 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
122 if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?;
123 Ok(Default::default())
124 }
125}
126
127impl Cheatcode for readDir_0Call {
128 fn apply(&self, state: &mut Cheatcodes) -> Result {
129 let Self { path } = self;
130 read_dir(state, path.as_ref(), 1, false)
131 }
132}
133
134impl Cheatcode for readDir_1Call {
135 fn apply(&self, state: &mut Cheatcodes) -> Result {
136 let Self { path, maxDepth } = self;
137 read_dir(state, path.as_ref(), *maxDepth, false)
138 }
139}
140
141impl Cheatcode for readDir_2Call {
142 fn apply(&self, state: &mut Cheatcodes) -> Result {
143 let Self { path, maxDepth, followLinks } = self;
144 read_dir(state, path.as_ref(), *maxDepth, *followLinks)
145 }
146}
147
148impl Cheatcode for readFileCall {
149 fn apply(&self, state: &mut Cheatcodes) -> Result {
150 let Self { path } = self;
151 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
152 Ok(fs::locked_read_to_string(path)?.abi_encode())
153 }
154}
155
156impl Cheatcode for readFileBinaryCall {
157 fn apply(&self, state: &mut Cheatcodes) -> Result {
158 let Self { path } = self;
159 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
160 Ok(fs::locked_read(path)?.abi_encode())
161 }
162}
163
164impl Cheatcode for readLineCall {
165 fn apply(&self, state: &mut Cheatcodes) -> Result {
166 let Self { path } = self;
167 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
168
169 let reader = match state.test_context.opened_read_files.entry(path.clone()) {
171 Entry::Occupied(entry) => entry.into_mut(),
172 Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)),
173 };
174
175 let mut line: String = String::new();
176 reader.read_line(&mut line)?;
177
178 if line.ends_with('\n') {
180 line.pop();
181 if line.ends_with('\r') {
182 line.pop();
183 }
184 }
185
186 Ok(line.abi_encode())
187 }
188}
189
190impl Cheatcode for readLinkCall {
191 fn apply(&self, state: &mut Cheatcodes) -> Result {
192 let Self { linkPath: path } = self;
193 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
194 let target = fs::read_link(path)?;
195 Ok(target.display().to_string().abi_encode())
196 }
197}
198
199impl Cheatcode for removeDirCall {
200 fn apply(&self, state: &mut Cheatcodes) -> Result {
201 let Self { path, recursive } = self;
202 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
203 if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?;
204 Ok(Default::default())
205 }
206}
207
208impl Cheatcode for removeFileCall {
209 fn apply(&self, state: &mut Cheatcodes) -> Result {
210 let Self { path } = self;
211 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
212 state.config.ensure_not_foundry_toml(&path)?;
213
214 state.test_context.opened_read_files.remove(&path);
216
217 if state.fs_commit {
218 fs::remove_file(&path)?;
219 }
220
221 Ok(Default::default())
222 }
223}
224
225impl Cheatcode for writeFileCall {
226 fn apply(&self, state: &mut Cheatcodes) -> Result {
227 let Self { path, data } = self;
228 write_file(state, path.as_ref(), data.as_bytes())
229 }
230}
231
232impl Cheatcode for writeFileBinaryCall {
233 fn apply(&self, state: &mut Cheatcodes) -> Result {
234 let Self { path, data } = self;
235 write_file(state, path.as_ref(), data)
236 }
237}
238
239impl Cheatcode for writeLineCall {
240 fn apply(&self, state: &mut Cheatcodes) -> Result {
241 let Self { path, data: line } = self;
242 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
243 state.config.ensure_not_foundry_toml(&path)?;
244
245 if state.fs_commit {
246 fs::locked_write_line(path, line)?;
247 }
248
249 Ok(Default::default())
250 }
251}
252
253impl Cheatcode for getArtifactPathByCodeCall {
254 fn apply(&self, state: &mut Cheatcodes) -> Result {
255 let Self { code } = self;
256 let (artifact_id, _) = state
257 .config
258 .available_artifacts
259 .as_ref()
260 .and_then(|artifacts| artifacts.find_by_creation_code(code))
261 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
262
263 Ok(artifact_id.path.to_string_lossy().abi_encode())
264 }
265}
266
267impl Cheatcode for getArtifactPathByDeployedCodeCall {
268 fn apply(&self, state: &mut Cheatcodes) -> Result {
269 let Self { deployedCode } = self;
270 let (artifact_id, _) = state
271 .config
272 .available_artifacts
273 .as_ref()
274 .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode))
275 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
276
277 Ok(artifact_id.path.to_string_lossy().abi_encode())
278 }
279}
280
281impl Cheatcode for getCodeCall {
282 fn apply(&self, state: &mut Cheatcodes) -> Result {
283 let Self { artifactPath: path } = self;
284 Ok(get_artifact_code(state, path, false)?.abi_encode())
285 }
286}
287
288impl Cheatcode for getDeployedCodeCall {
289 fn apply(&self, state: &mut Cheatcodes) -> Result {
290 let Self { artifactPath: path } = self;
291 Ok(get_artifact_code(state, path, true)?.abi_encode())
292 }
293}
294
295impl Cheatcode for deployCode_0Call {
296 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
297 let Self { artifactPath: path } = self;
298 deploy_code(ccx, executor, path, None, None, None)
299 }
300}
301
302impl Cheatcode for deployCode_1Call {
303 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
304 let Self { artifactPath: path, constructorArgs: args } = self;
305 deploy_code(ccx, executor, path, Some(args), None, None)
306 }
307}
308
309impl Cheatcode for deployCode_2Call {
310 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
311 let Self { artifactPath: path, value } = self;
312 deploy_code(ccx, executor, path, None, Some(*value), None)
313 }
314}
315
316impl Cheatcode for deployCode_3Call {
317 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
318 let Self { artifactPath: path, constructorArgs: args, value } = self;
319 deploy_code(ccx, executor, path, Some(args), Some(*value), None)
320 }
321}
322
323impl Cheatcode for deployCode_4Call {
324 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
325 let Self { artifactPath: path, salt } = self;
326 deploy_code(ccx, executor, path, None, None, Some((*salt).into()))
327 }
328}
329
330impl Cheatcode for deployCode_5Call {
331 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
332 let Self { artifactPath: path, constructorArgs: args, salt } = self;
333 deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into()))
334 }
335}
336
337impl Cheatcode for deployCode_6Call {
338 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
339 let Self { artifactPath: path, value, salt } = self;
340 deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into()))
341 }
342}
343
344impl Cheatcode for deployCode_7Call {
345 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
346 let Self { artifactPath: path, constructorArgs: args, value, salt } = self;
347 deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into()))
348 }
349}
350
351fn deploy_code(
354 ccx: &mut CheatsCtxt,
355 executor: &mut dyn CheatcodesExecutor,
356 path: &str,
357 constructor_args: Option<&Bytes>,
358 value: Option<U256>,
359 salt: Option<U256>,
360) -> Result {
361 let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
362 if let Some(args) = constructor_args {
363 bytecode.extend_from_slice(args);
364 }
365
366 let scheme =
367 if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create };
368
369 let outcome = executor.exec_create(
370 CreateInputs {
371 caller: ccx.caller,
372 scheme,
373 value: value.unwrap_or(U256::ZERO),
374 init_code: bytecode.into(),
375 gas_limit: ccx.gas_limit,
376 },
377 ccx,
378 )?;
379
380 if !outcome.result.result.is_ok() {
381 return Err(crate::Error::from(outcome.result.output));
382 }
383
384 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
385
386 Ok(address.abi_encode())
387}
388
389fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<Bytes> {
400 let path = if path.ends_with(".json") {
401 PathBuf::from(path)
402 } else {
403 let mut parts = path.split(':');
404
405 let mut file = None;
406 let mut contract_name = None;
407 let mut version = None;
408
409 let path_or_name = parts.next().unwrap();
410 if path_or_name.contains('.') {
411 file = Some(PathBuf::from(path_or_name));
412 if let Some(name_or_version) = parts.next() {
413 if name_or_version.contains('.') {
414 version = Some(name_or_version);
415 } else {
416 contract_name = Some(name_or_version);
417 version = parts.next();
418 }
419 }
420 } else {
421 contract_name = Some(path_or_name);
422 version = parts.next();
423 }
424
425 let version = if let Some(version) = version {
426 Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?)
427 } else {
428 None
429 };
430
431 if let Some(artifacts) = &state.config.available_artifacts {
433 let filtered = artifacts
434 .iter()
435 .filter(|(id, _)| {
436 let id_name = id.name.split('.').next().unwrap();
438
439 if let Some(path) = &file
440 && !id.source.ends_with(path)
441 {
442 return false;
443 }
444 if let Some(name) = contract_name
445 && id_name != name
446 {
447 return false;
448 }
449 if let Some(ref version) = version
450 && (id.version.minor != version.minor
451 || id.version.major != version.major
452 || id.version.patch != version.patch)
453 {
454 return false;
455 }
456 true
457 })
458 .collect::<Vec<_>>();
459
460 let artifact = match &filtered[..] {
461 [] => Err(fmt_err!("no matching artifact found")),
462 [artifact] => Ok(*artifact),
463 filtered => {
464 let mut filtered = filtered.to_vec();
465 state
467 .config
468 .running_artifact
469 .as_ref()
470 .and_then(|running| {
471 filtered.retain(|(id, _)| id.version == running.version);
473
474 if filtered.len() == 1 {
476 return Some(filtered[0]);
477 }
478
479 filtered.retain(|(id, _)| id.profile == running.profile);
481
482 if filtered.len() == 1 { Some(filtered[0]) } else { None }
483 })
484 .ok_or_else(|| fmt_err!("multiple matching artifacts found"))
485 }
486 }?;
487
488 let maybe_bytecode = if deployed {
489 artifact.1.deployed_bytecode().cloned()
490 } else {
491 artifact.1.bytecode().cloned()
492 };
493
494 return maybe_bytecode
495 .ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"));
496 } else {
497 let path_in_artifacts =
498 match (file.map(|f| f.to_string_lossy().to_string()), contract_name) {
499 (Some(file), Some(contract_name)) => {
500 PathBuf::from(format!("{file}/{contract_name}.json"))
501 }
502 (None, Some(contract_name)) => {
503 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
504 }
505 (Some(file), None) => {
506 let name = file.replace(".sol", "");
507 PathBuf::from(format!("{file}/{name}.json"))
508 }
509 _ => bail!("invalid artifact path"),
510 };
511
512 state.config.paths.artifacts.join(path_in_artifacts)
513 }
514 };
515
516 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
517 let data = fs::read_to_string(path)?;
518 let artifact = serde_json::from_str::<ContractObject>(&data)?;
519 let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode };
520 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
521}
522
523impl Cheatcode for ffiCall {
524 fn apply(&self, state: &mut Cheatcodes) -> Result {
525 let Self { commandInput: input } = self;
526
527 let output = ffi(state, input)?;
528
529 if output.exitCode != 0 {
531 return Err(fmt_err!(
533 "ffi command {:?} exited with code {}. stderr: {}",
534 input,
535 output.exitCode,
536 String::from_utf8_lossy(&output.stderr)
537 ));
538 }
539
540 if !output.stderr.is_empty() {
542 let stderr = String::from_utf8_lossy(&output.stderr);
543 warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr");
544 }
545
546 Ok(output.stdout.abi_encode())
548 }
549}
550
551impl Cheatcode for tryFfiCall {
552 fn apply(&self, state: &mut Cheatcodes) -> Result {
553 let Self { commandInput: input } = self;
554 ffi(state, input).map(|res| res.abi_encode())
555 }
556}
557
558impl Cheatcode for promptCall {
559 fn apply(&self, state: &mut Cheatcodes) -> Result {
560 let Self { promptText: text } = self;
561 prompt(state, text, prompt_input).map(|res| res.abi_encode())
562 }
563}
564
565impl Cheatcode for promptSecretCall {
566 fn apply(&self, state: &mut Cheatcodes) -> Result {
567 let Self { promptText: text } = self;
568 prompt(state, text, prompt_password).map(|res| res.abi_encode())
569 }
570}
571
572impl Cheatcode for promptSecretUintCall {
573 fn apply(&self, state: &mut Cheatcodes) -> Result {
574 let Self { promptText: text } = self;
575 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
576 }
577}
578
579impl Cheatcode for promptAddressCall {
580 fn apply(&self, state: &mut Cheatcodes) -> Result {
581 let Self { promptText: text } = self;
582 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
583 }
584}
585
586impl Cheatcode for promptUintCall {
587 fn apply(&self, state: &mut Cheatcodes) -> Result {
588 let Self { promptText: text } = self;
589 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
590 }
591}
592
593pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result {
594 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
595 state.config.ensure_not_foundry_toml(&path)?;
597
598 if state.fs_commit {
599 fs::locked_write(path, contents)?;
600 }
601
602 Ok(Default::default())
603}
604
605fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) -> Result {
606 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
607 let paths: Vec<DirEntry> = WalkDir::new(root)
608 .min_depth(1)
609 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
610 .follow_links(follow_links)
611 .contents_first(false)
612 .same_file_system(true)
613 .sort_by_file_name()
614 .into_iter()
615 .map(|entry| match entry {
616 Ok(entry) => DirEntry {
617 errorMessage: String::new(),
618 path: entry.path().display().to_string(),
619 depth: entry.depth() as u64,
620 isDir: entry.file_type().is_dir(),
621 isSymlink: entry.path_is_symlink(),
622 },
623 Err(e) => DirEntry {
624 errorMessage: e.to_string(),
625 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
626 depth: e.depth() as u64,
627 isDir: false,
628 isSymlink: false,
629 },
630 })
631 .collect();
632 Ok(paths.abi_encode())
633}
634
635fn ffi(state: &Cheatcodes, input: &[String]) -> Result<FfiResult> {
636 ensure!(
637 state.config.ffi,
638 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
639 );
640 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
641 let mut cmd = Command::new(&input[0]);
642 cmd.args(&input[1..]);
643
644 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
645
646 let output = cmd
647 .current_dir(&state.config.root)
648 .output()
649 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
650
651 let trimmed_stdout = String::from_utf8(output.stdout)?;
654 let trimmed_stdout = trimmed_stdout.trim();
655 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
656 hex
657 } else {
658 trimmed_stdout.as_bytes().to_vec()
659 };
660 Ok(FfiResult {
661 exitCode: output.status.code().unwrap_or(69),
662 stdout: encoded_stdout.into(),
663 stderr: output.stderr.into(),
664 })
665}
666
667fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
668 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
669}
670
671fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
672 Password::new().with_prompt(prompt_text).interact()
673}
674
675fn prompt(
676 state: &Cheatcodes,
677 prompt_text: &str,
678 input: fn(&str) -> Result<String, dialoguer::Error>,
679) -> Result<String> {
680 let text_clone = prompt_text.to_string();
681 let timeout = state.config.prompt_timeout;
682 let (tx, rx) = mpsc::channel();
683
684 thread::spawn(move || {
685 let _ = tx.send(input(&text_clone));
686 });
687
688 match rx.recv_timeout(timeout) {
689 Ok(res) => res.map_err(|err| {
690 let _ = sh_println!();
691 err.to_string().into()
692 }),
693 Err(_) => {
694 let _ = sh_eprintln!();
695 Err("Prompt timed out".into())
696 }
697 }
698}
699
700impl Cheatcode for getBroadcastCall {
701 fn apply(&self, state: &mut Cheatcodes) -> Result {
702 let Self { contractName, chainId, txType } = self;
703
704 let latest_broadcast = latest_broadcast(
705 contractName,
706 *chainId,
707 &state.config.broadcast,
708 vec![map_broadcast_tx_type(*txType)],
709 )?;
710
711 Ok(latest_broadcast.abi_encode())
712 }
713}
714
715impl Cheatcode for getBroadcasts_0Call {
716 fn apply(&self, state: &mut Cheatcodes) -> Result {
717 let Self { contractName, chainId, txType } = self;
718
719 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
720 .with_tx_type(map_broadcast_tx_type(*txType));
721
722 let broadcasts = reader.read()?;
723
724 let summaries = broadcasts
725 .into_iter()
726 .flat_map(|broadcast| {
727 let results = reader.into_tx_receipts(broadcast);
728 parse_broadcast_results(results)
729 })
730 .collect::<Vec<_>>();
731
732 Ok(summaries.abi_encode())
733 }
734}
735
736impl Cheatcode for getBroadcasts_1Call {
737 fn apply(&self, state: &mut Cheatcodes) -> Result {
738 let Self { contractName, chainId } = self;
739
740 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
741
742 let broadcasts = reader.read()?;
743
744 let summaries = broadcasts
745 .into_iter()
746 .flat_map(|broadcast| {
747 let results = reader.into_tx_receipts(broadcast);
748 parse_broadcast_results(results)
749 })
750 .collect::<Vec<_>>();
751
752 Ok(summaries.abi_encode())
753 }
754}
755
756impl Cheatcode for getDeployment_0Call {
757 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
758 let Self { contractName } = self;
759 let chain_id = ccx.ecx.cfg.chain_id;
760
761 let latest_broadcast = latest_broadcast(
762 contractName,
763 chain_id,
764 &ccx.state.config.broadcast,
765 vec![CallKind::Create, CallKind::Create2],
766 )?;
767
768 Ok(latest_broadcast.contractAddress.abi_encode())
769 }
770}
771
772impl Cheatcode for getDeployment_1Call {
773 fn apply(&self, state: &mut Cheatcodes) -> Result {
774 let Self { contractName, chainId } = self;
775
776 let latest_broadcast = latest_broadcast(
777 contractName,
778 *chainId,
779 &state.config.broadcast,
780 vec![CallKind::Create, CallKind::Create2],
781 )?;
782
783 Ok(latest_broadcast.contractAddress.abi_encode())
784 }
785}
786
787impl Cheatcode for getDeploymentsCall {
788 fn apply(&self, state: &mut Cheatcodes) -> Result {
789 let Self { contractName, chainId } = self;
790
791 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
792 .with_tx_type(CallKind::Create)
793 .with_tx_type(CallKind::Create2);
794
795 let broadcasts = reader.read()?;
796
797 let summaries = broadcasts
798 .into_iter()
799 .flat_map(|broadcast| {
800 let results = reader.into_tx_receipts(broadcast);
801 parse_broadcast_results(results)
802 })
803 .collect::<Vec<_>>();
804
805 let deployed_addresses =
806 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
807
808 Ok(deployed_addresses.abi_encode())
809 }
810}
811
812fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
813 match tx_type {
814 BroadcastTxType::Call => CallKind::Call,
815 BroadcastTxType::Create => CallKind::Create,
816 BroadcastTxType::Create2 => CallKind::Create2,
817 _ => unreachable!("invalid tx type"),
818 }
819}
820
821fn parse_broadcast_results(
822 results: Vec<(TransactionWithMetadata, AnyTransactionReceipt)>,
823) -> Vec<BroadcastTxSummary> {
824 results
825 .into_iter()
826 .map(|(tx, receipt)| BroadcastTxSummary {
827 txHash: receipt.transaction_hash,
828 blockNumber: receipt.block_number.unwrap_or_default(),
829 txType: match tx.opcode {
830 CallKind::Call => BroadcastTxType::Call,
831 CallKind::Create => BroadcastTxType::Create,
832 CallKind::Create2 => BroadcastTxType::Create2,
833 _ => unreachable!("invalid tx type"),
834 },
835 contractAddress: tx.contract_address.unwrap_or_default(),
836 success: receipt.status(),
837 })
838 .collect()
839}
840
841fn latest_broadcast(
842 contract_name: &String,
843 chain_id: u64,
844 broadcast_path: &Path,
845 filters: Vec<CallKind>,
846) -> Result<BroadcastTxSummary> {
847 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
848
849 for filter in filters {
850 reader = reader.with_tx_type(filter);
851 }
852
853 let broadcast = reader.read_latest()?;
854
855 let results = reader.into_tx_receipts(broadcast);
856
857 let summaries = parse_broadcast_results(results);
858
859 summaries
860 .first()
861 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
862 .cloned()
863}
864
865#[cfg(test)]
866mod tests {
867 use super::*;
868 use crate::CheatsConfig;
869 use std::sync::Arc;
870
871 fn cheats() -> Cheatcodes {
872 let config = CheatsConfig {
873 ffi: true,
874 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
875 ..Default::default()
876 };
877 Cheatcodes::new(Arc::new(config))
878 }
879
880 #[test]
881 fn test_ffi_hex() {
882 let msg = b"gm";
883 let cheats = cheats();
884 let args = ["echo".to_string(), hex::encode(msg)];
885 let output = ffi(&cheats, &args).unwrap();
886 assert_eq!(output.stdout, Bytes::from(msg));
887 }
888
889 #[test]
890 fn test_ffi_string() {
891 let msg = "gm";
892 let cheats = cheats();
893 let args = ["echo".to_string(), msg.to_string()];
894 let output = ffi(&cheats, &args).unwrap();
895 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
896 }
897
898 #[test]
899 fn test_ffi_fails_on_error_code() {
900 let mut cheats = cheats();
901
902 #[cfg(unix)]
904 let args = vec!["false".to_string()];
905 #[cfg(windows)]
906 let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()];
907
908 let result = ffiCall { commandInput: args }.apply(&mut cheats);
909
910 assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded");
912
913 let err_msg = result.unwrap_err().to_string();
915 assert!(
916 err_msg.contains("exited with code 1"),
917 "Error message did not contain exit code: {err_msg}"
918 );
919 }
920
921 #[test]
922 fn test_artifact_parsing() {
923 let s = include_str!("../../evm/test-data/solc-obj.json");
924 let artifact: ContractObject = serde_json::from_str(s).unwrap();
925 assert!(artifact.bytecode.is_some());
926
927 let artifact: ContractObject = serde_json::from_str(s).unwrap();
928 assert!(artifact.deployed_bytecode.is_some());
929 }
930}