Skip to main content

foundry_cheatcodes_spec/
lib.rs

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