foundry_cheatcodes_spec/
lib.rs1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![allow(rustdoc::bare_urls, rustdoc::invalid_html_tags)]
7
8use serde::{Deserialize, Serialize};
9use std::{borrow::Cow, fmt};
10
11mod cheatcode;
12pub use cheatcode::{Cheatcode, CheatcodeDef, Group, Safety, Status};
13
14mod function;
15pub use function::{Function, Mutability, Visibility};
16
17mod items;
18pub use items::{Enum, EnumVariant, Error, Event, Struct, StructField};
19
20mod symbolic;
21pub use symbolic::SymbolicVm;
22
23mod vm;
24pub use vm::Vm;
25
26#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[serde(rename_all = "camelCase")]
31pub struct Cheatcodes<'a> {
32 #[serde(borrow)]
34 pub errors: Cow<'a, [Error<'a>]>,
35 #[serde(borrow)]
37 pub events: Cow<'a, [Event<'a>]>,
38 #[serde(borrow)]
40 pub enums: Cow<'a, [Enum<'a>]>,
41 #[serde(borrow)]
43 pub structs: Cow<'a, [Struct<'a>]>,
44 #[serde(borrow)]
46 pub cheatcodes: Cow<'a, [Cheatcode<'a>]>,
47}
48
49impl fmt::Display for Cheatcodes<'_> {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 for error in self.errors.iter() {
52 writeln!(f, "{error}")?;
53 }
54 for event in self.events.iter() {
55 writeln!(f, "{event}")?;
56 }
57 for enumm in self.enums.iter() {
58 writeln!(f, "{enumm}")?;
59 }
60 for strukt in self.structs.iter() {
61 writeln!(f, "{strukt}")?;
62 }
63 for cheatcode in self.cheatcodes.iter() {
64 writeln!(f, "{}", cheatcode.func)?;
65 }
66 Ok(())
67 }
68}
69
70impl Default for Cheatcodes<'static> {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl Cheatcodes<'static> {
77 pub fn new() -> Self {
79 Self {
80 structs: Cow::Owned(vec![
83 Vm::Log::STRUCT.clone(),
84 Vm::Rpc::STRUCT.clone(),
85 Vm::EthGetLogs::STRUCT.clone(),
86 Vm::EthStorageProof::STRUCT.clone(),
87 Vm::EthGetProof::STRUCT.clone(),
88 Vm::DirEntry::STRUCT.clone(),
89 Vm::FsMetadata::STRUCT.clone(),
90 Vm::Wallet::STRUCT.clone(),
91 Vm::FfiResult::STRUCT.clone(),
92 Vm::ChainInfo::STRUCT.clone(),
93 Vm::Chain::STRUCT.clone(),
94 Vm::AccountAccess::STRUCT.clone(),
95 Vm::StorageAccess::STRUCT.clone(),
96 Vm::Gas::STRUCT.clone(),
97 Vm::DebugStep::STRUCT.clone(),
98 Vm::BroadcastTxSummary::STRUCT.clone(),
99 Vm::SignedDelegation::STRUCT.clone(),
100 Vm::PotentialRevert::STRUCT.clone(),
101 Vm::AccessListItem::STRUCT.clone(),
102 ]),
103 enums: Cow::Owned(vec![
104 Vm::CallerMode::ENUM.clone(),
105 Vm::AccountAccessKind::ENUM.clone(),
106 Vm::ForgeContext::ENUM.clone(),
107 Vm::BroadcastTxType::ENUM.clone(),
108 ]),
109 errors: Vm::VM_ERRORS.iter().copied().cloned().collect(),
110 events: Cow::Borrowed(&[]),
111 cheatcodes: Vm::CHEATCODES.iter().copied().cloned().collect(),
113 }
114 }
115}
116
117#[cfg(test)]
118#[expect(clippy::disallowed_macros)]
119mod tests {
120 use super::*;
121 use std::{fs, path::Path};
122
123 const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.json");
124 #[cfg(feature = "schema")]
125 const SCHEMA_PATH: &str =
126 concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json");
127 const IFACE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/utils/Vm.sol");
128
129 fn json_cheatcodes() -> String {
131 serde_json::to_string_pretty(&Cheatcodes::new()).unwrap()
132 }
133
134 #[cfg(feature = "schema")]
136 fn json_schema() -> String {
137 serde_json::to_string_pretty(&schemars::schema_for!(Cheatcodes<'_>)).unwrap()
138 }
139
140 fn sol_iface() -> String {
141 let mut cheats = Cheatcodes::new();
142 cheats.errors = Default::default(); let cheats = cheats.to_string().trim().replace('\n', "\n ");
144 format!(
145 "\
146// Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually.
147// This interface is just for internal testing purposes. Use `forge-std` instead.
148
149// SPDX-License-Identifier: MIT OR Apache-2.0
150pragma solidity >=0.6.2 <0.9.0;
151pragma experimental ABIEncoderV2;
152
153interface Vm {{
154 {cheats}
155}}
156"
157 )
158 }
159
160 #[test]
161 fn spec_up_to_date() {
162 ensure_file_contents(Path::new(JSON_PATH), &json_cheatcodes());
163 }
164
165 #[test]
166 #[cfg(feature = "schema")]
167 fn schema_up_to_date() {
168 ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema());
169 }
170
171 #[test]
172 fn iface_up_to_date() {
173 ensure_file_contents(Path::new(IFACE_PATH), &sol_iface());
174 }
175
176 fn ensure_file_contents(file: &Path, contents: &str) {
179 if let Ok(old_contents) = fs::read_to_string(file)
180 && normalize_newlines(&old_contents) == normalize_newlines(contents)
181 {
182 return;
184 }
185
186 eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display());
187 if std::env::var("CI").is_ok() {
188 eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n");
189 }
190 if let Some(parent) = file.parent() {
191 let _ = fs::create_dir_all(parent);
192 }
193 fs::write(file, contents).unwrap();
194 panic!("some file was not up to date and has been updated, simply re-run the tests");
195 }
196
197 fn normalize_newlines(s: &str) -> String {
198 s.replace("\r\n", "\n")
199 }
200}