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