1use 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 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 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
404pub(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 if path == "." {
572 path = "$";
573 }
574 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 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
589pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
592 if path.starts_with('$') { path.into() } else { format!("${path}").into() }
593}
594
595#[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 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 fields
637 .iter()
638 .map(|(name, type_description)| {
639 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 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 if f.fract() == 0.0 {
674 let s = number.to_string();
680
681 if s.contains('e') {
686 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 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 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 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
805fn 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
826pub(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 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
853pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
866 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 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 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
909fn 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 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 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 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 fn custom_struct_strategy() -> impl Strategy<Value = (StructDefinitions, DynSolValue)> {
1027 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 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 ("[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 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 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 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 let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)";
1114
1115 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 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 let ty_desc = "Person(bool active,uint256 age,string name)";
1144
1145 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 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 let ty_desc = "Item(uint256 id,string name,uint256 price)";
1214
1215 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 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 let json_str = r#"{ "name": "Alice" }"#;
1246
1247 let type_description = "Person(uint256 age,string name)";
1249 let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1250
1251 let json_value: Value = serde_json::from_str(json_str).unwrap();
1253 let result = parse_json_as(&json_value, &ty);
1254
1255 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 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 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 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 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 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 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 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 let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap();
1337 assert_eq!(parsed_value, original_wallet);
1338 }
1339}