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::serialize_value_as_json, fs};
8use foundry_config::fs_permissions::FsAccessKind;
9use serde_json::{Map, Value};
10use std::{borrow::Cow, collections::BTreeMap};
11
12impl Cheatcode for keyExistsCall {
13    fn apply(&self, _state: &mut Cheatcodes) -> Result {
14        let Self { json, key } = self;
15        check_json_key_exists(json, key)
16    }
17}
18
19impl Cheatcode for keyExistsJsonCall {
20    fn apply(&self, _state: &mut Cheatcodes) -> Result {
21        let Self { json, key } = self;
22        check_json_key_exists(json, key)
23    }
24}
25
26impl Cheatcode for parseJson_0Call {
27    fn apply(&self, _state: &mut Cheatcodes) -> Result {
28        let Self { json } = self;
29        parse_json(json, "$")
30    }
31}
32
33impl Cheatcode for parseJson_1Call {
34    fn apply(&self, _state: &mut Cheatcodes) -> Result {
35        let Self { json, key } = self;
36        parse_json(json, key)
37    }
38}
39
40impl Cheatcode for parseJsonUintCall {
41    fn apply(&self, _state: &mut Cheatcodes) -> Result {
42        let Self { json, key } = self;
43        parse_json_coerce(json, key, &DynSolType::Uint(256))
44    }
45}
46
47impl Cheatcode for parseJsonUintArrayCall {
48    fn apply(&self, _state: &mut Cheatcodes) -> Result {
49        let Self { json, key } = self;
50        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256))))
51    }
52}
53
54impl Cheatcode for parseJsonIntCall {
55    fn apply(&self, _state: &mut Cheatcodes) -> Result {
56        let Self { json, key } = self;
57        parse_json_coerce(json, key, &DynSolType::Int(256))
58    }
59}
60
61impl Cheatcode for parseJsonIntArrayCall {
62    fn apply(&self, _state: &mut Cheatcodes) -> Result {
63        let Self { json, key } = self;
64        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256))))
65    }
66}
67
68impl Cheatcode for parseJsonBoolCall {
69    fn apply(&self, _state: &mut Cheatcodes) -> Result {
70        let Self { json, key } = self;
71        parse_json_coerce(json, key, &DynSolType::Bool)
72    }
73}
74
75impl Cheatcode for parseJsonBoolArrayCall {
76    fn apply(&self, _state: &mut Cheatcodes) -> Result {
77        let Self { json, key } = self;
78        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool)))
79    }
80}
81
82impl Cheatcode for parseJsonAddressCall {
83    fn apply(&self, _state: &mut Cheatcodes) -> Result {
84        let Self { json, key } = self;
85        parse_json_coerce(json, key, &DynSolType::Address)
86    }
87}
88
89impl Cheatcode for parseJsonAddressArrayCall {
90    fn apply(&self, _state: &mut Cheatcodes) -> Result {
91        let Self { json, key } = self;
92        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address)))
93    }
94}
95
96impl Cheatcode for parseJsonStringCall {
97    fn apply(&self, _state: &mut Cheatcodes) -> Result {
98        let Self { json, key } = self;
99        parse_json_coerce(json, key, &DynSolType::String)
100    }
101}
102
103impl Cheatcode for parseJsonStringArrayCall {
104    fn apply(&self, _state: &mut Cheatcodes) -> Result {
105        let Self { json, key } = self;
106        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String)))
107    }
108}
109
110impl Cheatcode for parseJsonBytesCall {
111    fn apply(&self, _state: &mut Cheatcodes) -> Result {
112        let Self { json, key } = self;
113        parse_json_coerce(json, key, &DynSolType::Bytes)
114    }
115}
116
117impl Cheatcode for parseJsonBytesArrayCall {
118    fn apply(&self, _state: &mut Cheatcodes) -> Result {
119        let Self { json, key } = self;
120        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes)))
121    }
122}
123
124impl Cheatcode for parseJsonBytes32Call {
125    fn apply(&self, _state: &mut Cheatcodes) -> Result {
126        let Self { json, key } = self;
127        parse_json_coerce(json, key, &DynSolType::FixedBytes(32))
128    }
129}
130
131impl Cheatcode for parseJsonBytes32ArrayCall {
132    fn apply(&self, _state: &mut Cheatcodes) -> Result {
133        let Self { json, key } = self;
134        parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32))))
135    }
136}
137
138impl Cheatcode for parseJsonType_0Call {
139    fn apply(&self, _state: &mut Cheatcodes) -> Result {
140        let Self { json, typeDescription } = self;
141        parse_json_coerce(json, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode())
142    }
143}
144
145impl Cheatcode for parseJsonType_1Call {
146    fn apply(&self, _state: &mut Cheatcodes) -> Result {
147        let Self { json, key, typeDescription } = self;
148        parse_json_coerce(json, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode())
149    }
150}
151
152impl Cheatcode for parseJsonTypeArrayCall {
153    fn apply(&self, _state: &mut Cheatcodes) -> Result {
154        let Self { json, key, typeDescription } = self;
155        let ty = resolve_type(typeDescription)?;
156        parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
157    }
158}
159
160impl Cheatcode for parseJsonKeysCall {
161    fn apply(&self, _state: &mut Cheatcodes) -> Result {
162        let Self { json, key } = self;
163        parse_json_keys(json, key)
164    }
165}
166
167impl Cheatcode for serializeJsonCall {
168    fn apply(&self, state: &mut Cheatcodes) -> Result {
169        let Self { objectKey, value } = self;
170        *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?;
171        Ok(value.abi_encode())
172    }
173}
174
175impl Cheatcode for serializeBool_0Call {
176    fn apply(&self, state: &mut Cheatcodes) -> Result {
177        let Self { objectKey, valueKey, value } = self;
178        serialize_json(state, objectKey, valueKey, (*value).into())
179    }
180}
181
182impl Cheatcode for serializeUint_0Call {
183    fn apply(&self, state: &mut Cheatcodes) -> Result {
184        let Self { objectKey, valueKey, value } = self;
185        serialize_json(state, objectKey, valueKey, (*value).into())
186    }
187}
188
189impl Cheatcode for serializeInt_0Call {
190    fn apply(&self, state: &mut Cheatcodes) -> Result {
191        let Self { objectKey, valueKey, value } = self;
192        serialize_json(state, objectKey, valueKey, (*value).into())
193    }
194}
195
196impl Cheatcode for serializeAddress_0Call {
197    fn apply(&self, state: &mut Cheatcodes) -> Result {
198        let Self { objectKey, valueKey, value } = self;
199        serialize_json(state, objectKey, valueKey, (*value).into())
200    }
201}
202
203impl Cheatcode for serializeBytes32_0Call {
204    fn apply(&self, state: &mut Cheatcodes) -> Result {
205        let Self { objectKey, valueKey, value } = self;
206        serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32))
207    }
208}
209
210impl Cheatcode for serializeString_0Call {
211    fn apply(&self, state: &mut Cheatcodes) -> Result {
212        let Self { objectKey, valueKey, value } = self;
213        serialize_json(state, objectKey, valueKey, value.clone().into())
214    }
215}
216
217impl Cheatcode for serializeBytes_0Call {
218    fn apply(&self, state: &mut Cheatcodes) -> Result {
219        let Self { objectKey, valueKey, value } = self;
220        serialize_json(state, objectKey, valueKey, value.to_vec().into())
221    }
222}
223
224impl Cheatcode for serializeBool_1Call {
225    fn apply(&self, state: &mut Cheatcodes) -> Result {
226        let Self { objectKey, valueKey, values } = self;
227        serialize_json(
228            state,
229            objectKey,
230            valueKey,
231            DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()),
232        )
233    }
234}
235
236impl Cheatcode for serializeUint_1Call {
237    fn apply(&self, state: &mut Cheatcodes) -> Result {
238        let Self { objectKey, valueKey, values } = self;
239        serialize_json(
240            state,
241            objectKey,
242            valueKey,
243            DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()),
244        )
245    }
246}
247
248impl Cheatcode for serializeInt_1Call {
249    fn apply(&self, state: &mut Cheatcodes) -> Result {
250        let Self { objectKey, valueKey, values } = self;
251        serialize_json(
252            state,
253            objectKey,
254            valueKey,
255            DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()),
256        )
257    }
258}
259
260impl Cheatcode for serializeAddress_1Call {
261    fn apply(&self, state: &mut Cheatcodes) -> Result {
262        let Self { objectKey, valueKey, values } = self;
263        serialize_json(
264            state,
265            objectKey,
266            valueKey,
267            DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()),
268        )
269    }
270}
271
272impl Cheatcode for serializeBytes32_1Call {
273    fn apply(&self, state: &mut Cheatcodes) -> Result {
274        let Self { objectKey, valueKey, values } = self;
275        serialize_json(
276            state,
277            objectKey,
278            valueKey,
279            DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()),
280        )
281    }
282}
283
284impl Cheatcode for serializeString_1Call {
285    fn apply(&self, state: &mut Cheatcodes) -> Result {
286        let Self { objectKey, valueKey, values } = self;
287        serialize_json(
288            state,
289            objectKey,
290            valueKey,
291            DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()),
292        )
293    }
294}
295
296impl Cheatcode for serializeBytes_1Call {
297    fn apply(&self, state: &mut Cheatcodes) -> Result {
298        let Self { objectKey, valueKey, values } = self;
299        serialize_json(
300            state,
301            objectKey,
302            valueKey,
303            DynSolValue::Array(
304                values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(),
305            ),
306        )
307    }
308}
309
310impl Cheatcode for serializeJsonType_0Call {
311    fn apply(&self, _state: &mut Cheatcodes) -> Result {
312        let Self { typeDescription, value } = self;
313        let ty = resolve_type(typeDescription)?;
314        let value = ty.abi_decode(value)?;
315        let value = serialize_value_as_json(value)?;
316        Ok(value.to_string().abi_encode())
317    }
318}
319
320impl Cheatcode for serializeJsonType_1Call {
321    fn apply(&self, state: &mut Cheatcodes) -> Result {
322        let Self { objectKey, valueKey, typeDescription, value } = self;
323        let ty = resolve_type(typeDescription)?;
324        let value = ty.abi_decode(value)?;
325        serialize_json(state, objectKey, valueKey, value)
326    }
327}
328
329impl Cheatcode for serializeUintToHexCall {
330    fn apply(&self, state: &mut Cheatcodes) -> Result {
331        let Self { objectKey, valueKey, value } = self;
332        let hex = format!("0x{value:x}");
333        serialize_json(state, objectKey, valueKey, hex.into())
334    }
335}
336
337impl Cheatcode for writeJson_0Call {
338    fn apply(&self, state: &mut Cheatcodes) -> Result {
339        let Self { json, path } = self;
340        let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned()));
341        let json_string = serde_json::to_string_pretty(&json)?;
342        super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
343    }
344}
345
346impl Cheatcode for writeJson_1Call {
347    fn apply(&self, state: &mut Cheatcodes) -> Result {
348        let Self { json: value, path, valueKey } = self;
349
350        // Read, parse, and update the JSON object
351        let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
352        let data_string = fs::read_to_string(&data_path)?;
353        let mut data =
354            serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string));
355        upsert_json_value(&mut data, value, valueKey)?;
356
357        // Write the updated content back to the file
358        let json_string = serde_json::to_string_pretty(&data)?;
359        super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
360    }
361}
362
363pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result {
364    let json = parse_json_str(json)?;
365    let values = select(&json, key)?;
366    let exists = !values.is_empty();
367    Ok(exists.abi_encode())
368}
369
370pub(super) fn parse_json(json: &str, path: &str) -> Result {
371    let value = parse_json_str(json)?;
372    let selected = select(&value, path)?;
373    let sol = json_to_sol(&selected)?;
374    Ok(encode(sol))
375}
376
377pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result {
378    let json = parse_json_str(json)?;
379    let [value] = select(&json, path)?[..] else {
380        bail!("path {path:?} must return exactly one JSON value");
381    };
382
383    parse_json_as(value, ty).map(|v| v.abi_encode())
384}
385
386/// Parses given [serde_json::Value] as a [DynSolValue].
387pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result<DynSolValue> {
388    let to_string = |v: &Value| {
389        let mut s = v.to_string();
390        s.retain(|c: char| c != '"');
391        s
392    };
393
394    match (value, ty) {
395        (Value::Array(array), ty) => parse_json_array(array, ty),
396        (Value::Object(object), ty) => parse_json_map(object, ty),
397        (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
398        (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty),
399        _ => string::parse_value(&to_string(value), ty),
400    }
401}
402
403pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result<DynSolValue> {
404    match ty {
405        DynSolType::Tuple(types) => {
406            ensure!(array.len() == types.len(), "array length mismatch");
407            let values = array
408                .iter()
409                .zip(types)
410                .map(|(e, ty)| parse_json_as(e, ty))
411                .collect::<Result<Vec<_>>>()?;
412
413            Ok(DynSolValue::Tuple(values))
414        }
415        DynSolType::Array(inner) => {
416            let values =
417                array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
418            Ok(DynSolValue::Array(values))
419        }
420        DynSolType::FixedArray(inner, len) => {
421            ensure!(array.len() == *len, "array length mismatch");
422            let values =
423                array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
424            Ok(DynSolValue::FixedArray(values))
425        }
426        _ => bail!("expected {ty}, found array"),
427    }
428}
429
430pub(super) fn parse_json_map(map: &Map<String, Value>, ty: &DynSolType) -> Result<DynSolValue> {
431    let Some((name, fields, types)) = ty.as_custom_struct() else {
432        bail!("expected {ty}, found JSON object");
433    };
434
435    let mut values = Vec::with_capacity(fields.len());
436    for (field, ty) in fields.iter().zip(types.iter()) {
437        let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") };
438        values.push(parse_json_as(value, ty)?);
439    }
440
441    Ok(DynSolValue::CustomStruct {
442        name: name.to_string(),
443        prop_names: fields.to_vec(),
444        tuple: values,
445    })
446}
447
448pub(super) fn parse_json_keys(json: &str, key: &str) -> Result {
449    let json = parse_json_str(json)?;
450    let values = select(&json, key)?;
451    let [value] = values[..] else {
452        bail!("key {key:?} must return exactly one JSON object");
453    };
454    let Value::Object(object) = value else {
455        bail!("JSON value at {key:?} is not an object");
456    };
457    let keys = object.keys().collect::<Vec<_>>();
458    Ok(keys.abi_encode())
459}
460
461fn parse_json_str(json: &str) -> Result<Value> {
462    serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}"))
463}
464
465fn json_to_sol(json: &[&Value]) -> Result<Vec<DynSolValue>> {
466    let mut sol = Vec::with_capacity(json.len());
467    for value in json {
468        sol.push(json_value_to_token(value)?);
469    }
470    Ok(sol)
471}
472
473fn select<'a>(value: &'a Value, mut path: &str) -> Result<Vec<&'a Value>> {
474    // Handle the special case of the root key
475    if path == "." {
476        path = "$";
477    }
478    // format error with debug string because json_path errors may contain newlines
479    jsonpath_lib::select(value, &canonicalize_json_path(path))
480        .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string()))
481}
482
483fn encode(values: Vec<DynSolValue>) -> Vec<u8> {
484    // Double `abi_encode` is intentional
485    let bytes = match &values[..] {
486        [] => Vec::new(),
487        [one] => one.abi_encode(),
488        _ => DynSolValue::Array(values).abi_encode(),
489    };
490    bytes.abi_encode()
491}
492
493/// Canonicalize a json path key to always start from the root of the document.
494/// Read more about json path syntax: <https://goessner.net/articles/JsonPath/>
495pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
496    if !path.starts_with('$') { format!("${path}").into() } else { path.into() }
497}
498
499/// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer
500/// decoding, use [`parse_json_as`].
501///
502/// The function is designed to run recursively, so that in case of an object
503/// it will call itself to convert each of it's value and encode the whole as a
504/// Tuple
505pub(super) fn json_value_to_token(value: &Value) -> Result<DynSolValue> {
506    match value {
507        Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)),
508        Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)),
509        Value::Array(array) => {
510            array.iter().map(json_value_to_token).collect::<Result<_>>().map(DynSolValue::Array)
511        }
512        value @ Value::Object(_) => {
513            // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647)
514            let ordered_object: BTreeMap<String, Value> =
515                serde_json::from_value(value.clone()).unwrap();
516            ordered_object
517                .values()
518                .map(json_value_to_token)
519                .collect::<Result<_>>()
520                .map(DynSolValue::Tuple)
521        }
522        Value::Number(number) => {
523            if let Some(f) = number.as_f64() {
524                // Check if the number has decimal digits because the EVM does not support floating
525                // point math
526                if f.fract() == 0.0 {
527                    // Use the string representation of the `serde_json` Number type instead of
528                    // calling f.to_string(), because some numbers are wrongly rounded up after
529                    // being convented to f64.
530                    // Example: 18446744073709551615 becomes 18446744073709552000 after parsing it
531                    // to f64.
532                    let s = number.to_string();
533
534                    // Coerced to scientific notation, so short-circuit to using fallback.
535                    // This will not have a problem with hex numbers, as for parsing these
536                    // We'd need to prefix this with 0x.
537                    // See also <https://docs.soliditylang.org/en/latest/types.html#rational-and-integer-literals>
538                    if s.contains('e') {
539                        // Calling Number::to_string with powers of ten formats the number using
540                        // scientific notation and causes from_dec_str to fail. Using format! with
541                        // f64 keeps the full number representation.
542                        // Example: 100000000000000000000 becomes 1e20 when Number::to_string is
543                        // used.
544                        let fallback_s = f.to_string();
545                        if let Ok(n) = fallback_s.parse() {
546                            return Ok(DynSolValue::Uint(n, 256));
547                        }
548                        if let Ok(n) = I256::from_dec_str(&fallback_s) {
549                            return Ok(DynSolValue::Int(n, 256));
550                        }
551                    }
552
553                    if let Ok(n) = s.parse() {
554                        return Ok(DynSolValue::Uint(n, 256));
555                    }
556                    if let Ok(n) = s.parse() {
557                        return Ok(DynSolValue::Int(n, 256));
558                    }
559                }
560            }
561
562            Err(fmt_err!("unsupported JSON number: {number}"))
563        }
564        Value::String(string) => {
565            // Handle hex strings
566            if let Some(mut val) = string.strip_prefix("0x") {
567                let s;
568                if val.len() == 39 {
569                    return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into());
570                }
571                if !val.len().is_multiple_of(2) {
572                    s = format!("0{val}");
573                    val = &s[..];
574                }
575                if let Ok(bytes) = hex::decode(val) {
576                    return Ok(match bytes.len() {
577                        20 => DynSolValue::Address(Address::from_slice(&bytes)),
578                        32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32),
579                        _ => DynSolValue::Bytes(bytes),
580                    });
581                }
582            }
583
584            // Handle large numbers that were potentially encoded as strings because they exceed the
585            // capacity of a 64-bit integer.
586            // Note that number-like strings that *could* fit in an `i64`/`u64` will fall through
587            // and be treated as literal strings.
588            if let Ok(n) = string.parse::<I256>()
589                && i64::try_from(n).is_err()
590            {
591                return Ok(DynSolValue::Int(n, 256));
592            } else if let Ok(n) = string.parse::<U256>()
593                && u64::try_from(n).is_err()
594            {
595                return Ok(DynSolValue::Uint(n, 256));
596            }
597
598            // Otherwise, treat as a regular string
599            Ok(DynSolValue::String(string.to_owned()))
600        }
601    }
602}
603
604/// Serializes a key:value pair to a specific object. If the key is valueKey, the value is
605/// expected to be an object, which will be set as the root object for the provided object key,
606/// overriding the whole root object if the object key already exists. By calling this function
607/// multiple times, the user can serialize multiple KV pairs to the same object. The value can be of
608/// any type, even a new object in itself. The function will return a stringified version of the
609/// object, so that the user can use that as a value to a new invocation of the same function with a
610/// new object key. This enables the user to reuse the same function to crate arbitrarily complex
611/// object structures (JSON).
612fn serialize_json(
613    state: &mut Cheatcodes,
614    object_key: &str,
615    value_key: &str,
616    value: DynSolValue,
617) -> Result {
618    let value = serialize_value_as_json(value)?;
619    let map = state.serialized_jsons.entry(object_key.into()).or_default();
620    map.insert(value_key.into(), value);
621    let stringified = serde_json::to_string(map).unwrap();
622    Ok(stringified.abi_encode())
623}
624
625/// Resolves a [DynSolType] from user input.
626pub(super) fn resolve_type(type_description: &str) -> Result<DynSolType> {
627    if let Ok(ty) = DynSolType::parse(type_description) {
628        return Ok(ty);
629    };
630
631    if let Ok(encoded) = EncodeType::parse(type_description) {
632        let main_type = encoded.types[0].type_name;
633        let mut resolver = Resolver::default();
634        for t in encoded.types {
635            resolver.ingest(t.to_owned());
636        }
637
638        return Ok(resolver.resolve(main_type)?);
639    };
640
641    bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string")
642}
643
644/// Upserts a value into a JSON object based on a dot-separated key.
645///
646/// This function navigates through a mutable `serde_json::Value` object using a
647/// path-like key. It creates nested JSON objects if they do not exist along the path.
648/// The value is inserted at the final key in the path.
649///
650/// # Arguments
651///
652/// * `data` - A mutable reference to the `serde_json::Value` to be modified.
653/// * `value` - The string representation of the value to upsert. This string is first parsed as
654///   JSON, and if that fails, it's treated as a plain JSON string.
655/// * `key` - A dot-separated string representing the path to the location for upserting.
656pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
657    // Parse the path key into segments.
658    let canonical_key = canonicalize_json_path(key);
659    let parts: Vec<&str> = canonical_key
660        .strip_prefix("$.")
661        .unwrap_or(key)
662        .split('.')
663        .filter(|s| !s.is_empty())
664        .collect();
665
666    if parts.is_empty() {
667        return Err(fmt_err!("'valueKey' cannot be empty or just '$'"));
668    }
669
670    // Separate the final key from the path.
671    // Traverse the objects, creating intermediary ones if necessary.
672    if let Some((key_to_insert, path_to_parent)) = parts.split_last() {
673        let mut current_level = data;
674
675        for segment in path_to_parent {
676            if !current_level.is_object() {
677                return Err(fmt_err!("path segment '{segment}' does not resolve to an object."));
678            }
679            current_level = current_level
680                .as_object_mut()
681                .unwrap()
682                .entry(segment.to_string())
683                .or_insert(Value::Object(Map::new()));
684        }
685
686        // Upsert the new value
687        if let Some(parent_obj) = current_level.as_object_mut() {
688            parent_obj.insert(
689                key_to_insert.to_string(),
690                serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())),
691            );
692        } else {
693            return Err(fmt_err!("final destination is not an object, cannot insert key."));
694        }
695    }
696
697    Ok(())
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703    use alloy_primitives::FixedBytes;
704    use proptest::strategy::Strategy;
705    use serde_json::json;
706
707    fn contains_tuple(value: &DynSolValue) -> bool {
708        match value {
709            DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => true,
710            DynSolValue::Array(v) | DynSolValue::FixedArray(v) => {
711                v.first().is_some_and(contains_tuple)
712            }
713            _ => false,
714        }
715    }
716
717    /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and
718    /// [DynSolValue::Address] respectively. Thus, we can't distinguish between address and bytes of
719    /// length 20 during decoding. Because of that, there are issues with handling of arrays of
720    /// those types.
721    fn fixup_guessable(value: DynSolValue) -> DynSolValue {
722        match value {
723            DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => {
724                if let Some(DynSolValue::Bytes(_)) = v.first() {
725                    v.retain(|v| {
726                        let len = v.as_bytes().unwrap().len();
727                        len != 32 && len != 20
728                    })
729                }
730                DynSolValue::Array(v.into_iter().map(fixup_guessable).collect())
731            }
732            DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32),
733            DynSolValue::Bytes(v) if v.len() == 32 => {
734                DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32)
735            }
736            DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)),
737            _ => value,
738        }
739    }
740
741    fn guessable_types() -> impl proptest::strategy::Strategy<Value = DynSolValue> {
742        proptest::arbitrary::any::<DynSolValue>()
743            .prop_map(fixup_guessable)
744            .prop_filter("tuples are not supported", |v| !contains_tuple(v))
745            .prop_filter("filter out values without type", |v| v.as_type().is_some())
746    }
747
748    // Tests to ensure that conversion [DynSolValue] -> [serde_json::Value] -> [DynSolValue]
749    use proptest::prelude::ProptestConfig;
750    proptest::proptest! {
751        #![proptest_config(ProptestConfig {
752            cases: 99,
753            // These are flaky so persisting them is not useful in CI.
754            failure_persistence: None,
755            ..Default::default()
756        })]
757
758        #[test]
759        fn test_json_roundtrip_guessed(v in guessable_types()) {
760            let json = serialize_value_as_json(v.clone()).unwrap();
761            let value = json_value_to_token(&json).unwrap();
762
763            // do additional abi_encode -> abi_decode to avoid zero signed integers getting decoded as unsigned and causing assert_eq to fail.
764            let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap();
765            assert_eq!(decoded, v);
766        }
767
768        #[test]
769        fn test_json_roundtrip(v in proptest::arbitrary::any::<DynSolValue>().prop_filter("filter out values without type", |v| v.as_type().is_some())) {
770            let json = serialize_value_as_json(v.clone()).unwrap();
771            let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap();
772            assert_eq!(value, v);
773        }
774    }
775
776    #[test]
777    fn test_upsert_json_value() {
778        // Tuples of: (initial_json, key, value_to_upsert, expected)
779        let scenarios = vec![
780            // Simple key-value insert with a plain string
781            (json!({}), "foo", r#""bar""#, json!({"foo": "bar"})),
782            // Overwrite existing value with a number
783            (json!({"foo": "bar"}), "foo", "123", json!({"foo": 123})),
784            // Create nested objects
785            (json!({}), "a.b.c", r#""baz""#, json!({"a": {"b": {"c": "baz"}}})),
786            // Upsert into existing nested object with a boolean
787            (json!({"a": {"b": {}}}), "a.b.c", "true", json!({"a": {"b": {"c": true}}})),
788            // Upsert a JSON object as a value
789            (json!({}), "a.b", r#"{"d": "e"}"#, json!({"a": {"b": {"d": "e"}}})),
790            // Upsert a JSON array as a value
791            (json!({}), "myArray", r#"[1, "test", null]"#, json!({"myArray": [1, "test", null]})),
792        ];
793
794        for (mut initial, key, value_str, expected) in scenarios {
795            upsert_json_value(&mut initial, value_str, key).unwrap();
796            assert_eq!(initial, expected);
797        }
798
799        let error_scenarios = vec![
800            // Path traverses a non-object value
801            (
802                json!({"a": "a string value"}),
803                "a.b",
804                r#""bar""#,
805                "final destination is not an object, cannot insert key.",
806            ),
807            // Empty key should fail
808            (json!({}), "", r#""bar""#, "'valueKey' cannot be empty or just '$'"),
809            // Root path with a trailing dot should fail
810            (json!({}), "$.", r#""bar""#, "'valueKey' cannot be empty or just '$'"),
811        ];
812
813        for (mut initial, key, value_str, error_msg) in error_scenarios {
814            let result = upsert_json_value(&mut initial, value_str, key);
815            assert!(result.is_err(), "Expected an error for key: '{key}' but got Ok");
816            assert!(
817                result.unwrap_err().to_string().contains(error_msg),
818                "Error message for key '{key}' did not contain '{error_msg}'"
819            );
820        }
821    }
822}