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// Cheatcode docs are Solidity descriptions exported verbatim, such as `<path>:<contract>`.
6#![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// The `cheatcodes.json` schema.
27/// Foundry cheatcodes. Learn more: <https://book.getfoundry.sh/cheatcodes/>
28#[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    /// Cheatcode errors.
33    #[serde(borrow)]
34    pub errors: Cow<'a, [Error<'a>]>,
35    /// Cheatcode events.
36    #[serde(borrow)]
37    pub events: Cow<'a, [Event<'a>]>,
38    /// Cheatcode enums.
39    #[serde(borrow)]
40    pub enums: Cow<'a, [Enum<'a>]>,
41    /// Cheatcode structs.
42    #[serde(borrow)]
43    pub structs: Cow<'a, [Struct<'a>]>,
44    /// All the cheatcodes.
45    #[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    /// Returns the default cheatcodes.
78    pub fn new() -> Self {
79        Self {
80            // unfortunately technology has not yet advanced to the point where we can get all
81            // items of a certain type in a module, so we have to hardcode them here
82            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            // events: Vm::VM_EVENTS.iter().copied().cloned().collect(),
112            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    /// Generates the `cheatcodes.json` file contents.
130    fn json_cheatcodes() -> String {
131        serde_json::to_string_pretty(&Cheatcodes::new()).unwrap()
132    }
133
134    /// Generates the [cheatcodes](json_cheatcodes) JSON schema.
135    #[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(); // Skip errors to allow <0.8.4.
143        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    /// Checks that the `file` has the specified `contents`. If that is not the
177    /// case, updates the file and then fails the test.
178    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            // File is already up to date.
183            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}