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_coerce_default,
8        parse_json_keys, resolve_type, 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
48macro_rules! impl_parse_toml {
49    ($call:ident, $call_with_default:ident, $ty:expr) => {
50        impl Cheatcode for $call {
51            fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
52                let Self { toml, key } = self;
53                parse_toml_coerce(toml, key, &$ty)
54            }
55        }
56
57        impl Cheatcode for $call_with_default {
58            fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
59                let Self { toml, key, defaultValue } = self;
60                parse_toml_coerce_default(toml, key, &$ty, defaultValue)
61            }
62        }
63    };
64}
65
66impl_parse_toml!(parseTomlUint_0Call, parseTomlUint_1Call, DynSolType::Uint(256));
67impl_parse_toml!(
68    parseTomlUintArray_0Call,
69    parseTomlUintArray_1Call,
70    DynSolType::Array(Box::new(DynSolType::Uint(256)))
71);
72impl_parse_toml!(parseTomlInt_0Call, parseTomlInt_1Call, DynSolType::Int(256));
73impl_parse_toml!(
74    parseTomlIntArray_0Call,
75    parseTomlIntArray_1Call,
76    DynSolType::Array(Box::new(DynSolType::Int(256)))
77);
78impl_parse_toml!(parseTomlBool_0Call, parseTomlBool_1Call, DynSolType::Bool);
79impl_parse_toml!(
80    parseTomlBoolArray_0Call,
81    parseTomlBoolArray_1Call,
82    DynSolType::Array(Box::new(DynSolType::Bool))
83);
84impl_parse_toml!(parseTomlAddress_0Call, parseTomlAddress_1Call, DynSolType::Address);
85impl_parse_toml!(
86    parseTomlAddressArray_0Call,
87    parseTomlAddressArray_1Call,
88    DynSolType::Array(Box::new(DynSolType::Address))
89);
90impl_parse_toml!(parseTomlString_0Call, parseTomlString_1Call, DynSolType::String);
91impl_parse_toml!(
92    parseTomlStringArray_0Call,
93    parseTomlStringArray_1Call,
94    DynSolType::Array(Box::new(DynSolType::String))
95);
96impl_parse_toml!(parseTomlBytes_0Call, parseTomlBytes_1Call, DynSolType::Bytes);
97impl_parse_toml!(
98    parseTomlBytesArray_0Call,
99    parseTomlBytesArray_1Call,
100    DynSolType::Array(Box::new(DynSolType::Bytes))
101);
102impl_parse_toml!(parseTomlBytes32_0Call, parseTomlBytes32_1Call, DynSolType::FixedBytes(32));
103impl_parse_toml!(
104    parseTomlBytes32Array_0Call,
105    parseTomlBytes32Array_1Call,
106    DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))
107);
108
109impl Cheatcode for parseTomlType_0Call {
110    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
111        let Self { toml, typeDescription } = self;
112        parse_toml_coerce(
113            toml,
114            "$",
115            &resolve_type(
116                typeDescription,
117                state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
118            )?,
119        )
120        .map(|v| v.abi_encode())
121    }
122}
123
124impl Cheatcode for parseTomlType_1Call {
125    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
126        let Self { toml, key, typeDescription } = self;
127        parse_toml_coerce(
128            toml,
129            key,
130            &resolve_type(
131                typeDescription,
132                state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
133            )?,
134        )
135        .map(|v| v.abi_encode())
136    }
137}
138
139impl Cheatcode for parseTomlTypeArrayCall {
140    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
141        let Self { toml, key, typeDescription } = self;
142        let ty = resolve_type(
143            typeDescription,
144            state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()),
145        )?;
146        parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
147    }
148}
149
150impl Cheatcode for parseTomlKeysCall {
151    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
152        let Self { toml, key } = self;
153        parse_toml_keys(toml, key)
154    }
155}
156
157impl Cheatcode for writeToml_0Call {
158    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
159        let Self { json, path } = self;
160        let value =
161            serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned()));
162
163        let toml_string = format_json_to_toml(value)?;
164        super::fs::write_file(state, path.as_ref(), toml_string.as_bytes())
165    }
166}
167
168impl Cheatcode for writeToml_1Call {
169    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
170        let Self { json: value, path, valueKey } = self;
171
172        // Read and parse the TOML file.
173        // If the file doesn't exist, start with an empty object so the file is created.
174        let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
175        let mut json_data: JsonValue = if data_path.exists() {
176            let toml_data = fs::locked_read_to_string(&data_path)?;
177            toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?
178        } else {
179            JsonValue::Object(Default::default())
180        };
181        upsert_json_value(&mut json_data, value, valueKey)?;
182
183        // Serialize back to TOML and write the updated content back to the file
184        let toml_string = format_json_to_toml(json_data)?;
185        super::fs::write_file(state, path.as_ref(), toml_string.as_bytes())
186    }
187}
188
189/// Parse
190fn parse_toml_str(toml: &str) -> Result<TomlValue> {
191    toml::from_str(toml).map_err(|e| fmt_err!("failed parsing TOML: {e}"))
192}
193
194/// Parse a TOML string and return the value at the given path.
195fn parse_toml(toml: &str, key: &str, struct_defs: Option<&StructDefinitions>) -> Result {
196    parse_json(&toml_to_json_string(toml)?, key, struct_defs)
197}
198
199/// Parse a TOML string and return the value at the given path, coercing it to the given type.
200fn parse_toml_coerce(toml: &str, key: &str, ty: &DynSolType) -> Result {
201    parse_json_coerce(&toml_to_json_string(toml)?, key, ty)
202}
203
204/// Parse a TOML string and return the value at the given path, coercing it to the given type, or
205/// return the default if the path does not exist.
206fn parse_toml_coerce_default<T: SolValue>(
207    toml: &str,
208    key: &str,
209    ty: &DynSolType,
210    default: &T,
211) -> Result {
212    parse_json_coerce_default(&toml_to_json_string(toml)?, key, ty, default)
213}
214
215/// Parse a TOML string and return an array of all keys at the given path.
216fn parse_toml_keys(toml: &str, key: &str) -> Result {
217    parse_json_keys(&toml_to_json_string(toml)?, key)
218}
219
220/// Convert a TOML string to a JSON string.
221fn toml_to_json_string(toml: &str) -> Result<String> {
222    let toml = parse_toml_str(toml)?;
223    let json = toml_to_json_value(toml);
224    serde_json::to_string(&json).map_err(|e| fmt_err!("failed to serialize JSON: {e}"))
225}
226
227/// Format a JSON value to a TOML pretty string.
228fn format_json_to_toml(json: JsonValue) -> Result<String> {
229    let toml = json_to_toml_value(json);
230    toml::to_string_pretty(&toml).map_err(|e| fmt_err!("failed to serialize TOML: {e}"))
231}
232
233/// Convert a TOML value to a JSON value.
234pub(super) fn toml_to_json_value(toml: TomlValue) -> JsonValue {
235    match toml {
236        TomlValue::String(s) => match s.as_str() {
237            "null" => JsonValue::Null,
238            _ => JsonValue::String(s),
239        },
240        TomlValue::Integer(i) => JsonValue::Number(i.into()),
241        TomlValue::Float(f) => match serde_json::Number::from_f64(f) {
242            Some(n) => JsonValue::Number(n),
243            None => JsonValue::String(f.to_string()),
244        },
245        TomlValue::Boolean(b) => JsonValue::Bool(b),
246        TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()),
247        TomlValue::Table(t) => {
248            JsonValue::Object(t.into_iter().map(|(k, v)| (k, toml_to_json_value(v))).collect())
249        }
250        TomlValue::Datetime(d) => JsonValue::String(d.to_string()),
251    }
252}
253
254/// Convert a JSON value to a TOML value.
255fn json_to_toml_value(json: JsonValue) -> TomlValue {
256    match json {
257        JsonValue::String(s) => TomlValue::String(s),
258        JsonValue::Number(n) => match n.as_i64() {
259            Some(i) => TomlValue::Integer(i),
260            None => match n.as_f64() {
261                Some(f) => TomlValue::Float(f),
262                None => TomlValue::String(n.to_string()),
263            },
264        },
265        JsonValue::Bool(b) => TomlValue::Boolean(b),
266        JsonValue::Array(a) => TomlValue::Array(a.into_iter().map(json_to_toml_value).collect()),
267        JsonValue::Object(o) => {
268            TomlValue::Table(o.into_iter().map(|(k, v)| (k, json_to_toml_value(v))).collect())
269        }
270        JsonValue::Null => TomlValue::String("null".to_string()),
271    }
272}