Skip to main content

foundry_cheatcodes/
toml.rs

1//! Implementations of [`Toml`](spec::Group::Toml) cheatcodes.
2
3use crate::{
4    Cheatcode, Cheatcodes, Result,
5    Vm::*,
6    json::{
7        check_json_key_exists, parse_json, parse_json_coerce, parse_json_keys, resolve_type,
8        upsert_json_value,
9    },
10};
11use alloy_dyn_abi::DynSolType;
12use alloy_sol_types::SolValue;
13use foundry_common::{fmt::StructDefinitions, fs};
14use foundry_config::fs_permissions::FsAccessKind;
15use foundry_evm_core::evm::FoundryEvmNetwork;
16use serde_json::Value as JsonValue;
17use toml::Value as TomlValue;
18
19impl Cheatcode for keyExistsTomlCall {
20    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
21        let Self { toml, key } = self;
22        check_json_key_exists(&toml_to_json_string(toml)?, key)
23    }
24}
25
26impl Cheatcode for parseToml_0Call {
27    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
28        let Self { toml } = self;
29        parse_toml(
30            toml,
31            "$",
32            state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
33        )
34    }
35}
36
37impl Cheatcode for parseToml_1Call {
38    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
39        let Self { toml, key } = self;
40        parse_toml(
41            toml,
42            key,
43            state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
44        )
45    }
46}
47
48impl Cheatcode for parseTomlUintCall {
49    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
50        let Self { toml, key } = self;
51        parse_toml_coerce(toml, key, &DynSolType::Uint(256))
52    }
53}
54
55impl Cheatcode for parseTomlUintArrayCall {
56    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
57        let Self { toml, key } = self;
58        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256))))
59    }
60}
61
62impl Cheatcode for parseTomlIntCall {
63    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
64        let Self { toml, key } = self;
65        parse_toml_coerce(toml, key, &DynSolType::Int(256))
66    }
67}
68
69impl Cheatcode for parseTomlIntArrayCall {
70    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
71        let Self { toml, key } = self;
72        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256))))
73    }
74}
75
76impl Cheatcode for parseTomlBoolCall {
77    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
78        let Self { toml, key } = self;
79        parse_toml_coerce(toml, key, &DynSolType::Bool)
80    }
81}
82
83impl Cheatcode for parseTomlBoolArrayCall {
84    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
85        let Self { toml, key } = self;
86        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool)))
87    }
88}
89
90impl Cheatcode for parseTomlAddressCall {
91    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
92        let Self { toml, key } = self;
93        parse_toml_coerce(toml, key, &DynSolType::Address)
94    }
95}
96
97impl Cheatcode for parseTomlAddressArrayCall {
98    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
99        let Self { toml, key } = self;
100        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address)))
101    }
102}
103
104impl Cheatcode for parseTomlStringCall {
105    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
106        let Self { toml, key } = self;
107        parse_toml_coerce(toml, key, &DynSolType::String)
108    }
109}
110
111impl Cheatcode for parseTomlStringArrayCall {
112    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
113        let Self { toml, key } = self;
114        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String)))
115    }
116}
117
118impl Cheatcode for parseTomlBytesCall {
119    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
120        let Self { toml, key } = self;
121        parse_toml_coerce(toml, key, &DynSolType::Bytes)
122    }
123}
124
125impl Cheatcode for parseTomlBytesArrayCall {
126    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
127        let Self { toml, key } = self;
128        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes)))
129    }
130}
131
132impl Cheatcode for parseTomlBytes32Call {
133    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
134        let Self { toml, key } = self;
135        parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32))
136    }
137}
138
139impl Cheatcode for parseTomlBytes32ArrayCall {
140    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
141        let Self { toml, key } = self;
142        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32))))
143    }
144}
145
146impl Cheatcode for parseTomlType_0Call {
147    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
148        let Self { toml, typeDescription } = self;
149        parse_toml_coerce(
150            toml,
151            "$",
152            &resolve_type(
153                typeDescription,
154                state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
155            )?,
156        )
157        .map(|v| v.abi_encode())
158    }
159}
160
161impl Cheatcode for parseTomlType_1Call {
162    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
163        let Self { toml, key, typeDescription } = self;
164        parse_toml_coerce(
165            toml,
166            key,
167            &resolve_type(
168                typeDescription,
169                state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
170            )?,
171        )
172        .map(|v| v.abi_encode())
173    }
174}
175
176impl Cheatcode for parseTomlTypeArrayCall {
177    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
178        let Self { toml, key, typeDescription } = self;
179        let ty = resolve_type(
180            typeDescription,
181            state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
182        )?;
183        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
184    }
185}
186
187impl Cheatcode for parseTomlKeysCall {
188    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
189        let Self { toml, key } = self;
190        parse_toml_keys(toml, key)
191    }
192}
193
194impl Cheatcode for writeToml_0Call {
195    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
196        let Self { json, path } = self;
197        let value =
198            serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned()));
199
200        let toml_string = format_json_to_toml(value)?;
201        super::fs::write_file(state, path.as_ref(), toml_string.as_bytes())
202    }
203}
204
205impl Cheatcode for writeToml_1Call {
206    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
207        let Self { json: value, path, valueKey } = self;
208
209        // Read and parse the TOML file.
210        // If the file doesn't exist, start with an empty object so the file is created.
211        let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
212        let mut json_data: JsonValue = if data_path.exists() {
213            let toml_data = fs::locked_read_to_string(&data_path)?;
214            toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?
215        } else {
216            JsonValue::Object(Default::default())
217        };
218        upsert_json_value(&mut json_data, value, valueKey)?;
219
220        // Serialize back to TOML and write the updated content back to the file
221        let toml_string = format_json_to_toml(json_data)?;
222        super::fs::write_file(state, path.as_ref(), toml_string.as_bytes())
223    }
224}
225
226/// Parse
227fn parse_toml_str(toml: &str) -> Result<TomlValue> {
228    toml::from_str(toml).map_err(|e| fmt_err!("failed parsing TOML: {e}"))
229}
230
231/// Parse a TOML string and return the value at the given path.
232fn parse_toml(toml: &str, key: &str, struct_defs: Option<&StructDefinitions>) -> Result {
233    parse_json(&toml_to_json_string(toml)?, key, struct_defs)
234}
235
236/// Parse a TOML string and return the value at the given path, coercing it to the given type.
237fn parse_toml_coerce(toml: &str, key: &str, ty: &DynSolType) -> Result {
238    parse_json_coerce(&toml_to_json_string(toml)?, key, ty)
239}
240
241/// Parse a TOML string and return an array of all keys at the given path.
242fn parse_toml_keys(toml: &str, key: &str) -> Result {
243    parse_json_keys(&toml_to_json_string(toml)?, key)
244}
245
246/// Convert a TOML string to a JSON string.
247fn toml_to_json_string(toml: &str) -> Result<String> {
248    let toml = parse_toml_str(toml)?;
249    let json = toml_to_json_value(toml);
250    serde_json::to_string(&json).map_err(|e| fmt_err!("failed to serialize JSON: {e}"))
251}
252
253/// Format a JSON value to a TOML pretty string.
254fn format_json_to_toml(json: JsonValue) -> Result<String> {
255    let toml = json_to_toml_value(json);
256    toml::to_string_pretty(&toml).map_err(|e| fmt_err!("failed to serialize TOML: {e}"))
257}
258
259/// Convert a TOML value to a JSON value.
260pub(super) fn toml_to_json_value(toml: TomlValue) -> JsonValue {
261    match toml {
262        TomlValue::String(s) => match s.as_str() {
263            "null" => JsonValue::Null,
264            _ => JsonValue::String(s),
265        },
266        TomlValue::Integer(i) => JsonValue::Number(i.into()),
267        TomlValue::Float(f) => match serde_json::Number::from_f64(f) {
268            Some(n) => JsonValue::Number(n),
269            None => JsonValue::String(f.to_string()),
270        },
271        TomlValue::Boolean(b) => JsonValue::Bool(b),
272        TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()),
273        TomlValue::Table(t) => {
274            JsonValue::Object(t.into_iter().map(|(k, v)| (k, toml_to_json_value(v))).collect())
275        }
276        TomlValue::Datetime(d) => JsonValue::String(d.to_string()),
277    }
278}
279
280/// Convert a JSON value to a TOML value.
281fn json_to_toml_value(json: JsonValue) -> TomlValue {
282    match json {
283        JsonValue::String(s) => TomlValue::String(s),
284        JsonValue::Number(n) => match n.as_i64() {
285            Some(i) => TomlValue::Integer(i),
286            None => match n.as_f64() {
287                Some(f) => TomlValue::Float(f),
288                None => TomlValue::String(n.to_string()),
289            },
290        },
291        JsonValue::Bool(b) => TomlValue::Boolean(b),
292        JsonValue::Array(a) => TomlValue::Array(a.into_iter().map(json_to_toml_value).collect()),
293        JsonValue::Object(o) => {
294            TomlValue::Table(o.into_iter().map(|(k, v)| (k, json_to_toml_value(v))).collect())
295        }
296        JsonValue::Null => TomlValue::String("null".to_string()),
297    }
298}