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