Skip to main content

foundry_cheatcodes/
json.rs

1//! Implementations of [`Json`](spec::Group::Json) cheatcodes.
2
3use crate::{Cheatcode, Cheatcodes, Result, Vm::*, string};
4use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, eip712_parser::EncodeType};
5use alloy_primitives::{Address, B256, I256, U256, hex};
6use alloy_sol_types::SolValue;
7use foundry_common::{fmt::StructDefinitions, fs};
8use foundry_config::fs_permissions::FsAccessKind;
9use serde_json::{Map, Value};
10use std::{
11    borrow::Cow,
12    collections::{BTreeMap, BTreeSet},
13};
14
15impl Cheatcode for keyExistsCall {
16    fn apply(&self, _state: &mut Cheatcodes) -> Result {
17        let Self { json, key } = self;
18        check_json_key_exists(json, key)
19    }
20}
21
22impl Cheatcode for keyExistsJsonCall {
23    fn apply(&self, _state: &mut Cheatcodes) -> Result {
24        let Self { json, key } = self;
25        check_json_key_exists(json, key)
26    }
27}
28
29impl Cheatcode for parseJson_0Call {
30    fn apply(&self, state: &mut Cheatcodes) -> Result {
31        let Self { json } = self;
32        parse_json(json, "$", state.struct_defs())
33    }
34}
35
36impl Cheatcode for parseJson_1Call {
37    fn apply(&self, state: &mut Cheatcodes) -> Result {
38        let Self { json, key } = self;
39        parse_json(json, key, state.struct_defs())
40    }
41}
42
43impl Cheatcode for parseJsonUintCall {
44    fn apply(&self, _state: &mut Cheatcodes) -> Result {
45        let Self { json, key } = self;
46        parse_json_coerce(json, key, &DynSolType::Uint(256))
47    }
48}
49
50impl Cheatcode for parseJsonUintArrayCall {
51    fn apply(&self, _state: &mut Cheatcodes) -> Result {
52        let Self { json, key } = self;
53        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256))))
54    }
55}
56
57impl Cheatcode for parseJsonIntCall {
58    fn apply(&self, _state: &mut Cheatcodes) -> Result {
59        let Self { json, key } = self;
60        parse_json_coerce(json, key, &DynSolType::Int(256))
61    }
62}
63
64impl Cheatcode for parseJsonIntArrayCall {
65    fn apply(&self, _state: &mut Cheatcodes) -> Result {
66        let Self { json, key } = self;
67        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256))))
68    }
69}
70
71impl Cheatcode for parseJsonBoolCall {
72    fn apply(&self, _state: &mut Cheatcodes) -> Result {
73        let Self { json, key } = self;
74        parse_json_coerce(json, key, &DynSolType::Bool)
75    }
76}
77
78impl Cheatcode for parseJsonBoolArrayCall {
79    fn apply(&self, _state: &mut Cheatcodes) -> Result {
80        let Self { json, key } = self;
81        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool)))
82    }
83}
84
85impl Cheatcode for parseJsonAddressCall {
86    fn apply(&self, _state: &mut Cheatcodes) -> Result {
87        let Self { json, key } = self;
88        parse_json_coerce(json, key, &DynSolType::Address)
89    }
90}
91
92impl Cheatcode for parseJsonAddressArrayCall {
93    fn apply(&self, _state: &mut Cheatcodes) -> Result {
94        let Self { json, key } = self;
95        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address)))
96    }
97}
98
99impl Cheatcode for parseJsonStringCall {
100    fn apply(&self, _state: &mut Cheatcodes) -> Result {
101        let Self { json, key } = self;
102        parse_json_coerce(json, key, &DynSolType::String)
103    }
104}
105
106impl Cheatcode for parseJsonStringArrayCall {
107    fn apply(&self, _state: &mut Cheatcodes) -> Result {
108        let Self { json, key } = self;
109        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String)))
110    }
111}
112
113impl Cheatcode for parseJsonBytesCall {
114    fn apply(&self, _state: &mut Cheatcodes) -> Result {
115        let Self { json, key } = self;
116        parse_json_coerce(json, key, &DynSolType::Bytes)
117    }
118}
119
120impl Cheatcode for parseJsonBytesArrayCall {
121    fn apply(&self, _state: &mut Cheatcodes) -> Result {
122        let Self { json, key } = self;
123        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes)))
124    }
125}
126
127impl Cheatcode for parseJsonBytes32Call {
128    fn apply(&self, _state: &mut Cheatcodes) -> Result {
129        let Self { json, key } = self;
130        parse_json_coerce(json, key, &DynSolType::FixedBytes(32))
131    }
132}
133
134impl Cheatcode for parseJsonBytes32ArrayCall {
135    fn apply(&self, _state: &mut Cheatcodes) -> Result {
136        let Self { json, key } = self;
137        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32))))
138    }
139}
140
141impl Cheatcode for parseJsonType_0Call {
142    fn apply(&self, state: &mut Cheatcodes) -> Result {
143        let Self { json, typeDescription } = self;
144        parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?)
145            .map(|v| v.abi_encode())
146    }
147}
148
149impl Cheatcode for parseJsonType_1Call {
150    fn apply(&self, state: &mut Cheatcodes) -> Result {
151        let Self { json, key, typeDescription } = self;
152        parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?)
153            .map(|v| v.abi_encode())
154    }
155}
156
157impl Cheatcode for parseJsonTypeArrayCall {
158    fn apply(&self, state: &mut Cheatcodes) -> Result {
159        let Self { json, key, typeDescription } = self;
160        let ty = resolve_type(typeDescription, state.struct_defs())?;
161        parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
162    }
163}
164
165impl Cheatcode for parseJsonKeysCall {
166    fn apply(&self, _state: &mut Cheatcodes) -> Result {
167        let Self { json, key } = self;
168        parse_json_keys(json, key)
169    }
170}
171
172impl Cheatcode for serializeJsonCall {
173    fn apply(&self, state: &mut Cheatcodes) -> Result {
174        let Self { objectKey, value } = self;
175        *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?;
176        Ok(value.abi_encode())
177    }
178}
179
180impl Cheatcode for serializeBool_0Call {
181    fn apply(&self, state: &mut Cheatcodes) -> Result {
182        let Self { objectKey, valueKey, value } = self;
183        serialize_json(state, objectKey, valueKey, (*value).into())
184    }
185}
186
187impl Cheatcode for serializeUint_0Call {
188    fn apply(&self, state: &mut Cheatcodes) -> Result {
189        let Self { objectKey, valueKey, value } = self;
190        serialize_json(state, objectKey, valueKey, (*value).into())
191    }
192}
193
194impl Cheatcode for serializeInt_0Call {
195    fn apply(&self, state: &mut Cheatcodes) -> Result {
196        let Self { objectKey, valueKey, value } = self;
197        serialize_json(state, objectKey, valueKey, (*value).into())
198    }
199}
200
201impl Cheatcode for serializeAddress_0Call {
202    fn apply(&self, state: &mut Cheatcodes) -> Result {
203        let Self { objectKey, valueKey, value } = self;
204        serialize_json(state, objectKey, valueKey, (*value).into())
205    }
206}
207
208impl Cheatcode for serializeBytes32_0Call {
209    fn apply(&self, state: &mut Cheatcodes) -> Result {
210        let Self { objectKey, valueKey, value } = self;
211        serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32))
212    }
213}
214
215impl Cheatcode for serializeString_0Call {
216    fn apply(&self, state: &mut Cheatcodes) -> Result {
217        let Self { objectKey, valueKey, value } = self;
218        serialize_json(state, objectKey, valueKey, value.clone().into())
219    }
220}
221
222impl Cheatcode for serializeBytes_0Call {
223    fn apply(&self, state: &mut Cheatcodes) -> Result {
224        let Self { objectKey, valueKey, value } = self;
225        serialize_json(state, objectKey, valueKey, value.to_vec().into())
226    }
227}
228
229impl Cheatcode for serializeBool_1Call {
230    fn apply(&self, state: &mut Cheatcodes) -> Result {
231        let Self { objectKey, valueKey, values } = self;
232        serialize_json(
233            state,
234            objectKey,
235            valueKey,
236            DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()),
237        )
238    }
239}
240
241impl Cheatcode for serializeUint_1Call {
242    fn apply(&self, state: &mut Cheatcodes) -> Result {
243        let Self { objectKey, valueKey, values } = self;
244        serialize_json(
245            state,
246            objectKey,
247            valueKey,
248            DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()),
249        )
250    }
251}
252
253impl Cheatcode for serializeInt_1Call {
254    fn apply(&self, state: &mut Cheatcodes) -> Result {
255        let Self { objectKey, valueKey, values } = self;
256        serialize_json(
257            state,
258            objectKey,
259            valueKey,
260            DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()),
261        )
262    }
263}
264
265impl Cheatcode for serializeAddress_1Call {
266    fn apply(&self, state: &mut Cheatcodes) -> Result {
267        let Self { objectKey, valueKey, values } = self;
268        serialize_json(
269            state,
270            objectKey,
271            valueKey,
272            DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()),
273        )
274    }
275}
276
277impl Cheatcode for serializeBytes32_1Call {
278    fn apply(&self, state: &mut Cheatcodes) -> Result {
279        let Self { objectKey, valueKey, values } = self;
280        serialize_json(
281            state,
282            objectKey,
283            valueKey,
284            DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()),
285        )
286    }
287}
288
289impl Cheatcode for serializeString_1Call {
290    fn apply(&self, state: &mut Cheatcodes) -> Result {
291        let Self { objectKey, valueKey, values } = self;
292        serialize_json(
293            state,
294            objectKey,
295            valueKey,
296            DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()),
297        )
298    }
299}
300
301impl Cheatcode for serializeBytes_1Call {
302    fn apply(&self, state: &mut Cheatcodes) -> Result {
303        let Self { objectKey, valueKey, values } = self;
304        serialize_json(
305            state,
306            objectKey,
307            valueKey,
308            DynSolValue::Array(
309                values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(),
310            ),
311        )
312    }
313}
314
315impl Cheatcode for serializeJsonType_0Call {
316    fn apply(&self, state: &mut Cheatcodes) -> Result {
317        let Self { typeDescription, value } = self;
318        let ty = resolve_type(typeDescription, state.struct_defs())?;
319        let value = ty.abi_decode(value)?;
320        let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?;
321        Ok(value.to_string().abi_encode())
322    }
323}
324
325impl Cheatcode for serializeJsonType_1Call {
326    fn apply(&self, state: &mut Cheatcodes) -> Result {
327        let Self { objectKey, valueKey, typeDescription, value } = self;
328        let ty = resolve_type(typeDescription, state.struct_defs())?;
329        let value = ty.abi_decode(value)?;
330        serialize_json(state, objectKey, valueKey, value)
331    }
332}
333
334impl Cheatcode for serializeUintToHexCall {
335    fn apply(&self, state: &mut Cheatcodes) -> Result {
336        let Self { objectKey, valueKey, value } = self;
337        let hex = format!("0x{value:x}");
338        serialize_json(state, objectKey, valueKey, hex.into())
339    }
340}
341
342impl Cheatcode for writeJson_0Call {
343    fn apply(&self, state: &mut Cheatcodes) -> Result {
344        let Self { json, path } = self;
345        let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned()));
346        let json_string = serde_json::to_string_pretty(&json)?;
347        super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
348    }
349}
350
351impl Cheatcode for writeJson_1Call {
352    fn apply(&self, state: &mut Cheatcodes) -> Result {
353        let Self { json: value, path, valueKey } = self;
354
355        // Read, parse, and update the JSON object.
356        // If the file doesn't exist, start with an empty JSON object so the file is created.
357        let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
358        let mut data = if data_path.exists() {
359            let data_string = fs::locked_read_to_string(&data_path)?;
360            serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string))
361        } else {
362            Value::Object(Default::default())
363        };
364        upsert_json_value(&mut data, value, valueKey)?;
365
366        // Write the updated content back to the file
367        let json_string = serde_json::to_string_pretty(&data)?;
368        super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
369    }
370}
371
372pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result {
373    let json = parse_json_str(json)?;
374    let values = select(&json, key)?;
375    let exists = !values.is_empty();
376    Ok(exists.abi_encode())
377}
378
379pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result {
380    let value = parse_json_str(json)?;
381    let selected = select(&value, path)?;
382    let sol = json_to_sol(defs, &selected)?;
383    Ok(encode(sol))
384}
385
386pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result {
387    let json = parse_json_str(json)?;
388    let [value] = select(&json, path)?[..] else {
389        bail!("path {path:?} must return exactly one JSON value");
390    };
391
392    parse_json_as(value, ty).map(|v| v.abi_encode())
393}
394
395/// Parses given [serde_json::Value] as a [DynSolValue].
396pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result<DynSolValue> {
397    let to_string = |v: &Value| {
398        let mut s = v.to_string();
399        s.retain(|c: char| c != '"');
400        s
401    };
402
403    match (value, ty) {
404        (Value::Array(array), ty) => parse_json_array(array, ty),
405        (Value::Object(object), ty) => parse_json_map(object, ty),
406        (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
407        (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty),
408        _ => string::parse_value(&to_string(value), ty),
409    }
410}
411
412pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result<DynSolValue> {
413    match ty {
414        DynSolType::Tuple(types) => {
415            ensure!(array.len() == types.len(), "array length mismatch");
416            let values = array
417                .iter()
418                .zip(types)
419                .map(|(e, ty)| parse_json_as(e, ty))
420                .collect::<Result<Vec<_>>>()?;
421
422            Ok(DynSolValue::Tuple(values))
423        }
424        DynSolType::Array(inner) => {
425            let values =
426                array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
427            Ok(DynSolValue::Array(values))
428        }
429        DynSolType::FixedArray(inner, len) => {
430            ensure!(array.len() == *len, "array length mismatch");
431            let values =
432                array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
433            Ok(DynSolValue::FixedArray(values))
434        }
435        _ => bail!("expected {ty}, found array"),
436    }
437}
438
439pub(super) fn parse_json_map(map: &Map<String, Value>, ty: &DynSolType) -> Result<DynSolValue> {
440    let Some((name, fields, types)) = ty.as_custom_struct() else {
441        bail!("expected {ty}, found JSON object");
442    };
443
444    let mut values = Vec::with_capacity(fields.len());
445    for (field, ty) in fields.iter().zip(types.iter()) {
446        let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") };
447        values.push(parse_json_as(value, ty)?);
448    }
449
450    Ok(DynSolValue::CustomStruct {
451        name: name.to_string(),
452        prop_names: fields.to_vec(),
453        tuple: values,
454    })
455}
456
457pub(super) fn parse_json_keys(json: &str, key: &str) -> Result {
458    let json = parse_json_str(json)?;
459    let values = select(&json, key)?;
460    let [value] = values[..] else {
461        bail!("key {key:?} must return exactly one JSON object");
462    };
463    let Value::Object(object) = value else {
464        bail!("JSON value at {key:?} is not an object");
465    };
466    let keys = object.keys().collect::<Vec<_>>();
467    Ok(keys.abi_encode())
468}
469
470fn parse_json_str(json: &str) -> Result<Value> {
471    serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}"))
472}
473
474fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result<Vec<DynSolValue>> {
475    let mut sol = Vec::with_capacity(json.len());
476    for value in json {
477        sol.push(json_value_to_token(value, defs)?);
478    }
479    Ok(sol)
480}
481
482fn select<'a>(value: &'a Value, mut path: &str) -> Result<Vec<&'a Value>> {
483    // Handle the special case of the root key
484    if path == "." {
485        path = "$";
486    }
487    // format error with debug string because json_path errors may contain newlines
488    jsonpath_lib::select(value, &canonicalize_json_path(path))
489        .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string()))
490}
491
492fn encode(values: Vec<DynSolValue>) -> Vec<u8> {
493    // Double `abi_encode` is intentional
494    let bytes = match &values[..] {
495        [] => Vec::new(),
496        [one] => one.abi_encode(),
497        _ => DynSolValue::Array(values).abi_encode(),
498    };
499    bytes.abi_encode()
500}
501
502/// Canonicalize a json path key to always start from the root of the document.
503/// Read more about json path syntax: <https://goessner.net/articles/JsonPath/>
504pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
505    if !path.starts_with('$') { format!("${path}").into() } else { path.into() }
506}
507
508/// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer
509/// decoding, use [`parse_json_as`].
510///
511/// The function is designed to run recursively, so that in case of an object
512/// it will call itself to convert each of it's value and encode the whole as a
513/// Tuple
514#[instrument(target = "cheatcodes", level = "trace", ret)]
515pub(super) fn json_value_to_token(
516    value: &Value,
517    defs: Option<&StructDefinitions>,
518) -> Result<DynSolValue> {
519    if let Some(defs) = defs {
520        _json_value_to_token(value, defs)
521    } else {
522        _json_value_to_token(value, &StructDefinitions::default())
523    }
524}
525
526fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result<DynSolValue> {
527    match value {
528        Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)),
529        Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)),
530        Value::Array(array) => array
531            .iter()
532            .map(|v| _json_value_to_token(v, defs))
533            .collect::<Result<_>>()
534            .map(DynSolValue::Array),
535        Value::Object(map) => {
536            // Try to find a struct definition that matches the object keys.
537            let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect();
538            let matching_def = defs.values().find(|fields| {
539                fields.len() == keys.len()
540                    && fields.iter().map(|(name, _)| name.as_str()).collect::<BTreeSet<_>>() == keys
541            });
542
543            if let Some(fields) = matching_def {
544                // Found a struct with matching field names, use the order from the definition.
545                fields
546                    .iter()
547                    .map(|(name, _)| {
548                        // unwrap is safe because we know the key exists.
549                        _json_value_to_token(map.get(name).unwrap(), defs)
550                    })
551                    .collect::<Result<_>>()
552                    .map(DynSolValue::Tuple)
553            } else {
554                // Fallback to alphabetical sorting if no matching struct is found.
555                // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647)
556                let ordered_object: BTreeMap<_, _> =
557                    map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
558                ordered_object
559                    .values()
560                    .map(|value| _json_value_to_token(value, defs))
561                    .collect::<Result<_>>()
562                    .map(DynSolValue::Tuple)
563            }
564        }
565        Value::Number(number) => {
566            if let Some(f) = number.as_f64() {
567                // Check if the number has decimal digits because the EVM does not support floating
568                // point math
569                if f.fract() == 0.0 {
570                    // Use the string representation of the `serde_json` Number type instead of
571                    // calling f.to_string(), because some numbers are wrongly rounded up after
572                    // being convented to f64.
573                    // Example: 18446744073709551615 becomes 18446744073709552000 after parsing it
574                    // to f64.
575                    let s = number.to_string();
576
577                    // Coerced to scientific notation, so short-circuit to using fallback.
578                    // This will not have a problem with hex numbers, as for parsing these
579                    // We'd need to prefix this with 0x.
580                    // See also <https://docs.soliditylang.org/en/latest/types.html#rational-and-integer-literals>
581                    if s.contains('e') {
582                        // Calling Number::to_string with powers of ten formats the number using
583                        // scientific notation and causes from_dec_str to fail. Using format! with
584                        // f64 keeps the full number representation.
585                        // Example: 100000000000000000000 becomes 1e20 when Number::to_string is
586                        // used.
587                        let fallback_s = f.to_string();
588                        if let Ok(n) = fallback_s.parse() {
589                            return Ok(DynSolValue::Uint(n, 256));
590                        }
591                        if let Ok(n) = I256::from_dec_str(&fallback_s) {
592                            return Ok(DynSolValue::Int(n, 256));
593                        }
594                    }
595
596                    if let Ok(n) = s.parse() {
597                        return Ok(DynSolValue::Uint(n, 256));
598                    }
599                    if let Ok(n) = s.parse() {
600                        return Ok(DynSolValue::Int(n, 256));
601                    }
602                }
603            }
604
605            Err(fmt_err!("unsupported JSON number: {number}"))
606        }
607        Value::String(string) => {
608            //  Hanfl hex strings
609            if let Some(mut val) = string.strip_prefix("0x") {
610                let s;
611                if val.len() == 39 {
612                    return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into());
613                }
614                if !val.len().is_multiple_of(2) {
615                    s = format!("0{val}");
616                    val = &s[..];
617                }
618                if let Ok(bytes) = hex::decode(val) {
619                    return Ok(match bytes.len() {
620                        20 => DynSolValue::Address(Address::from_slice(&bytes)),
621                        32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32),
622                        _ => DynSolValue::Bytes(bytes),
623                    });
624                }
625            }
626
627            // Handle large numbers that were potentially encoded as strings because they exceed the
628            // capacity of a 64-bit integer.
629            // Note that number-like strings that *could* fit in an `i64`/`u64` will fall through
630            // and be treated as literal strings.
631            if let Ok(n) = string.parse::<I256>()
632                && i64::try_from(n).is_err()
633            {
634                return Ok(DynSolValue::Int(n, 256));
635            } else if let Ok(n) = string.parse::<U256>()
636                && u64::try_from(n).is_err()
637            {
638                return Ok(DynSolValue::Uint(n, 256));
639            }
640
641            // Otherwise, treat as a regular string
642            Ok(DynSolValue::String(string.to_owned()))
643        }
644    }
645}
646
647/// Serializes a key:value pair to a specific object. If the key is valueKey, the value is
648/// expected to be an object, which will be set as the root object for the provided object key,
649/// overriding the whole root object if the object key already exists. By calling this function
650/// multiple times, the user can serialize multiple KV pairs to the same object. The value can be of
651/// any type, even a new object in itself. The function will return a stringified version of the
652/// object, so that the user can use that as a value to a new invocation of the same function with a
653/// new object key. This enables the user to reuse the same function to crate arbitrarily complex
654/// object structures (JSON).
655fn serialize_json(
656    state: &mut Cheatcodes,
657    object_key: &str,
658    value_key: &str,
659    value: DynSolValue,
660) -> Result {
661    let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?;
662    let map = state.serialized_jsons.entry(object_key.into()).or_default();
663    map.insert(value_key.into(), value);
664    let stringified = serde_json::to_string(map).unwrap();
665    Ok(stringified.abi_encode())
666}
667
668/// Resolves a [DynSolType] from user input.
669pub(super) fn resolve_type(
670    type_description: &str,
671    struct_defs: Option<&StructDefinitions>,
672) -> Result<DynSolType> {
673    let ordered_ty = |ty| -> Result<DynSolType> {
674        if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) }
675    };
676
677    if let Ok(ty) = DynSolType::parse(type_description) {
678        return ordered_ty(ty);
679    };
680
681    if let Ok(encoded) = EncodeType::parse(type_description) {
682        let main_type = encoded.types[0].type_name;
683        let mut resolver = Resolver::default();
684        for t in &encoded.types {
685            resolver.ingest(t.to_owned());
686        }
687
688        // Get the alphabetically-sorted type from the resolver, and reorder if necessary.
689        return ordered_ty(resolver.resolve(main_type)?);
690    }
691
692    bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string")
693}
694
695/// Upserts a value into a JSON object based on a dot-separated key.
696///
697/// This function navigates through a mutable `serde_json::Value` object using a
698/// path-like key. It creates nested JSON objects if they do not exist along the path.
699/// The value is inserted at the final key in the path.
700///
701/// # Arguments
702///
703/// * `data` - A mutable reference to the `serde_json::Value` to be modified.
704/// * `value` - The string representation of the value to upsert. This string is first parsed as
705///   JSON, and if that fails, it's treated as a plain JSON string.
706/// * `key` - A dot-separated string representing the path to the location for upserting.
707pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
708    // Parse the path key into segments.
709    let canonical_key = canonicalize_json_path(key);
710    let parts: Vec<&str> = canonical_key
711        .strip_prefix("$.")
712        .unwrap_or(key)
713        .split('.')
714        .filter(|s| !s.is_empty())
715        .collect();
716
717    if parts.is_empty() {
718        return Err(fmt_err!("'valueKey' cannot be empty or just '$'"));
719    }
720
721    // Separate the final key from the path.
722    // Traverse the objects, creating intermediary ones if necessary.
723    if let Some((key_to_insert, path_to_parent)) = parts.split_last() {
724        let mut current_level = data;
725
726        for segment in path_to_parent {
727            if !current_level.is_object() {
728                return Err(fmt_err!("path segment '{segment}' does not resolve to an object."));
729            }
730            current_level = current_level
731                .as_object_mut()
732                .unwrap()
733                .entry(segment.to_string())
734                .or_insert(Value::Object(Map::new()));
735        }
736
737        // Upsert the new value
738        if let Some(parent_obj) = current_level.as_object_mut() {
739            parent_obj.insert(
740                key_to_insert.to_string(),
741                serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())),
742            );
743        } else {
744            return Err(fmt_err!("final destination is not an object, cannot insert key."));
745        }
746    }
747
748    Ok(())
749}
750
751/// Recursively traverses a `DynSolType` and reorders the fields of any
752/// `CustomStruct` variants according to the provided `StructDefinitions`.
753///
754/// This is necessary because the EIP-712 resolver sorts struct fields alphabetically,
755/// but we want to respect the order defined in the Solidity source code.
756fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result<DynSolType> {
757    match ty {
758        DynSolType::CustomStruct { name, prop_names, tuple } => {
759            if let Some(def) = struct_defs.get(&name)? {
760                // The incoming `prop_names` and `tuple` are alphabetically sorted.
761                let type_map: std::collections::HashMap<String, DynSolType> =
762                    prop_names.into_iter().zip(tuple).collect();
763
764                let mut sorted_props = Vec::with_capacity(def.len());
765                let mut sorted_tuple = Vec::with_capacity(def.len());
766                for (field_name, _) in def {
767                    sorted_props.push(field_name.clone());
768                    if let Some(field_ty) = type_map.get(field_name) {
769                        sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?);
770                    } else {
771                        bail!(
772                            "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'"
773                        );
774                    }
775                }
776                Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple })
777            } else {
778                // No definition found, so we can't reorder. However, we still reorder its children
779                // in case they have known structs.
780                let new_tuple = tuple
781                    .into_iter()
782                    .map(|t| reorder_type(t, struct_defs))
783                    .collect::<Result<Vec<_>>>()?;
784                Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple })
785            }
786        }
787        DynSolType::Array(inner) => {
788            Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?)))
789        }
790        DynSolType::FixedArray(inner, len) => {
791            Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len))
792        }
793        DynSolType::Tuple(inner) => Ok(DynSolType::Tuple(
794            inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::<Result<Vec<_>>>()?,
795        )),
796        _ => Ok(ty),
797    }
798}
799
800#[cfg(test)]
801mod tests {
802    use super::*;
803    use alloy_primitives::FixedBytes;
804    use foundry_common::fmt::{TypeDefMap, serialize_value_as_json};
805    use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
806    use std::collections::HashSet;
807
808    fn valid_value(value: &DynSolValue) -> bool {
809        (match value {
810            DynSolValue::String(s) if s == "{}" => false,
811
812            DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false,
813
814            DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value),
815            _ => true,
816        }) && value.as_type().is_some()
817    }
818
819    /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and
820    /// [DynSolValue::Address] respectively. Thus, we can't distinguish between address and bytes of
821    /// length 20 during decoding. Because of that, there are issues with handling of arrays of
822    /// those types.
823    fn fixup_guessable(value: DynSolValue) -> DynSolValue {
824        match value {
825            DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => {
826                if let Some(DynSolValue::Bytes(_)) = v.first() {
827                    v.retain(|v| {
828                        let len = v.as_bytes().unwrap().len();
829                        len != 32 && len != 20
830                    })
831                }
832                DynSolValue::Array(v.into_iter().map(fixup_guessable).collect())
833            }
834            DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32),
835            DynSolValue::Bytes(v) if v.len() == 32 => {
836                DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32)
837            }
838            DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)),
839            _ => value,
840        }
841    }
842
843    fn guessable_types() -> impl proptest::strategy::Strategy<Value = DynSolValue> {
844        any::<DynSolValue>().prop_map(fixup_guessable).prop_filter("invalid value", valid_value)
845    }
846
847    /// A proptest strategy for generating a (simple) `DynSolValue::CustomStruct`
848    /// and its corresponding `StructDefinitions` object.
849    fn custom_struct_strategy() -> impl Strategy<Value = (StructDefinitions, DynSolValue)> {
850        // Define a strategy for basic field names and values.
851        let field_name_strat = "[a-z]{4,12}";
852        let field_value_strat = prop_oneof![
853            any::<bool>().prop_map(DynSolValue::Bool),
854            any::<u32>().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)),
855            any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address),
856            any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)),
857            ".*".prop_map(DynSolValue::String),
858        ];
859
860        // Combine them to create a list of unique fields that preserve the random order.
861        let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8)
862            .prop_map(|fields| {
863                let mut unique_fields = Vec::with_capacity(fields.len());
864                let mut seen_names = HashSet::new();
865                for (name, value) in fields {
866                    if seen_names.insert(name.clone()) {
867                        unique_fields.push((name, value));
868                    }
869                }
870                unique_fields
871            });
872
873        // Generate the `CustomStruct` and its definition.
874        ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| {
875            let (prop_names, tuple): (Vec<String>, Vec<DynSolValue>) =
876                fields.clone().into_iter().unzip();
877            let def_fields: Vec<(String, String)> = fields
878                .iter()
879                .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string()))
880                .collect();
881            let mut defs_map = TypeDefMap::default();
882            defs_map.insert(struct_name.clone(), def_fields);
883            (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple })
884        })
885    }
886
887    // Tests to ensure that conversion [DynSolValue] -> [serde_json::Value] -> [DynSolValue]
888    proptest::proptest! {
889        #[test]
890        fn test_json_roundtrip_guessed(v in guessable_types()) {
891            let json = serialize_value_as_json(v.clone(), None).unwrap();
892            let value = json_value_to_token(&json, None).unwrap();
893
894            // do additional abi_encode -> abi_decode to avoid zero signed integers getting decoded as unsigned and causing assert_eq to fail.
895            let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap();
896            assert_eq!(decoded, v);
897        }
898
899        #[test]
900        fn test_json_roundtrip(v in any::<DynSolValue>().prop_filter("filter out values without type", |v| v.as_type().is_some())) {
901            let json = serialize_value_as_json(v.clone(), None).unwrap();
902            let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap();
903            assert_eq!(value, v);
904        }
905
906        #[test]
907        fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) {
908            let json = serialize_value_as_json(v.clone(), Some(&struct_defs)).unwrap();
909            let sol_type = v.as_type().unwrap();
910            let parsed_value = parse_json_as(&json, &sol_type).unwrap();
911            assert_eq!(parsed_value, v);
912        }
913    }
914
915    #[test]
916    fn test_resolve_type_with_definitions() -> Result<()> {
917        // Define a struct with fields in a specific order (not alphabetical)
918        let mut struct_defs = TypeDefMap::new();
919        struct_defs.insert(
920            "Apple".to_string(),
921            vec![
922                ("color".to_string(), "string".to_string()),
923                ("sweetness".to_string(), "uint8".to_string()),
924                ("sourness".to_string(), "uint8".to_string()),
925            ],
926        );
927        struct_defs.insert(
928            "FruitStall".to_string(),
929            vec![
930                ("name".to_string(), "string".to_string()),
931                ("apples".to_string(), "Apple[]".to_string()),
932            ],
933        );
934
935        // Simulate resolver output: type string, using alphabetical order for fields.
936        let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)";
937
938        // Resolve type and ensure struct definition order is preserved.
939        let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
940        if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
941            assert_eq!(name, "FruitStall");
942            assert_eq!(prop_names, vec!["name", "apples"]);
943            assert_eq!(tuple.len(), 2);
944            assert_eq!(tuple[0], DynSolType::String);
945
946            if let DynSolType::Array(apple_ty_boxed) = &tuple[1]
947                && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed
948            {
949                assert_eq!(*name, "Apple");
950                // Check that the inner struct's fields are also in definition order.
951                assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]);
952                assert_eq!(
953                    *tuple,
954                    vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)]
955                );
956
957                return Ok(());
958            }
959        }
960        panic!("Expected FruitStall and Apple to be CustomStruct");
961    }
962
963    #[test]
964    fn test_resolve_type_without_definitions() -> Result<()> {
965        // Simulate resolver output: type string, using alphabetical order for fields.
966        let ty_desc = "Person(bool active,uint256 age,string name)";
967
968        // Resolve the type without providing any struct definitions and ensure that original
969        // (alphabetical) order is unchanged.
970        let ty = resolve_type(ty_desc, None).unwrap();
971        if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
972            assert_eq!(name, "Person");
973            assert_eq!(prop_names, vec!["active", "age", "name"]);
974            assert_eq!(tuple.len(), 3);
975            assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]);
976            return Ok(());
977        }
978        panic!("Expected Person to be CustomStruct");
979    }
980
981    #[test]
982    fn test_resolve_type_for_array_of_structs() -> Result<()> {
983        // Define a struct with fields in a specific, non-alphabetical order.
984        let mut struct_defs = TypeDefMap::new();
985        struct_defs.insert(
986            "Item".to_string(),
987            vec![
988                ("name".to_string(), "string".to_string()),
989                ("price".to_string(), "uint256".to_string()),
990                ("id".to_string(), "uint256".to_string()),
991            ],
992        );
993
994        // Simulate resolver output: type string, using alphabetical order for fields.
995        let ty_desc = "Item(uint256 id,string name,uint256 price)";
996
997        // Resolve type and ensure struct definition order is preserved.
998        let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
999        let array_ty = DynSolType::Array(Box::new(ty));
1000        if let DynSolType::Array(item_ty) = array_ty
1001            && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty
1002        {
1003            assert_eq!(name, "Item");
1004            assert_eq!(prop_names, vec!["name", "price", "id"]);
1005            assert_eq!(
1006                tuple,
1007                vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)]
1008            );
1009            return Ok(());
1010        }
1011        panic!("Expected CustomStruct in array");
1012    }
1013
1014    #[test]
1015    fn test_parse_json_missing_field() {
1016        // Define a struct with a specific field order.
1017        let mut struct_defs = TypeDefMap::new();
1018        struct_defs.insert(
1019            "Person".to_string(),
1020            vec![
1021                ("name".to_string(), "string".to_string()),
1022                ("age".to_string(), "uint256".to_string()),
1023            ],
1024        );
1025
1026        // JSON missing the "age" field
1027        let json_str = r#"{ "name": "Alice" }"#;
1028
1029        // Simulate resolver output: type string, using alphabetical order for fields.
1030        let type_description = "Person(uint256 age,string name)";
1031        let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1032
1033        // Now, attempt to parse the incomplete JSON using the ordered type.
1034        let json_value: Value = serde_json::from_str(json_str).unwrap();
1035        let result = parse_json_as(&json_value, &ty);
1036
1037        // Should fail with a missing field error because `parse_json_map` requires all fields.
1038        assert!(result.is_err());
1039        assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object"));
1040    }
1041
1042    #[test]
1043    fn test_serialize_json_with_struct_def_order() {
1044        // Define a struct with a specific, non-alphabetical field order.
1045        let mut struct_defs = TypeDefMap::new();
1046        struct_defs.insert(
1047            "Item".to_string(),
1048            vec![
1049                ("name".to_string(), "string".to_string()),
1050                ("id".to_string(), "uint256".to_string()),
1051                ("active".to_string(), "bool".to_string()),
1052            ],
1053        );
1054
1055        // Create a DynSolValue instance for the struct.
1056        let item_struct = DynSolValue::CustomStruct {
1057            name: "Item".to_string(),
1058            prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()],
1059            tuple: vec![
1060                DynSolValue::String("Test Item".to_string()),
1061                DynSolValue::Uint(U256::from(123), 256),
1062                DynSolValue::Bool(true),
1063            ],
1064        };
1065
1066        // Serialize the value to JSON and verify that the order is preserved.
1067        let json_value = serialize_value_as_json(item_struct, Some(&struct_defs.into())).unwrap();
1068        let json_string = serde_json::to_string(&json_value).unwrap();
1069        assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#);
1070    }
1071
1072    #[test]
1073    fn test_json_full_cycle_typed_with_struct_defs() {
1074        // Define a struct with a specific, non-alphabetical field order.
1075        let mut struct_defs = TypeDefMap::new();
1076        struct_defs.insert(
1077            "Wallet".to_string(),
1078            vec![
1079                ("owner".to_string(), "address".to_string()),
1080                ("balance".to_string(), "uint256".to_string()),
1081                ("id".to_string(), "bytes32".to_string()),
1082            ],
1083        );
1084
1085        // Create the "original" DynSolValue instance.
1086        let owner_address = Address::from([1; 20]);
1087        let wallet_id = B256::from([2; 32]);
1088        let original_wallet = DynSolValue::CustomStruct {
1089            name: "Wallet".to_string(),
1090            prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()],
1091            tuple: vec![
1092                DynSolValue::Address(owner_address),
1093                DynSolValue::Uint(U256::from(5000), 256),
1094                DynSolValue::FixedBytes(wallet_id, 32),
1095            ],
1096        };
1097
1098        // Serialize it. The resulting JSON should respect the struct definition order.
1099        let json_value =
1100            serialize_value_as_json(original_wallet.clone(), Some(&struct_defs.clone().into()))
1101                .unwrap();
1102        let json_string = serde_json::to_string(&json_value).unwrap();
1103        assert_eq!(
1104            json_string,
1105            format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#)
1106        );
1107
1108        // Resolve the type, which should also respect the struct definition order.
1109        let type_description = "Wallet(uint256 balance,bytes32 id,address owner)";
1110        let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1111
1112        // Parse the JSON using the correctly ordered resolved type. Ensure that it is identical to
1113        // the original one.
1114        let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap();
1115        assert_eq!(parsed_value, original_wallet);
1116    }
1117}