Skip to main content

foundry_cheatcodes/
json.rs

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