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 serde_json::{Map, Value};
10use std::{
11 borrow::Cow,
12 collections::{BTreeMap, BTreeSet},
13};
14
15impl Cheatcode for keyExistsCall {
16 fn apply(&self, _state: &mut Cheatcodes) -> Result {
17 let Self { json, key } = self;
18 check_json_key_exists(json, key)
19 }
20}
21
22impl Cheatcode for keyExistsJsonCall {
23 fn apply(&self, _state: &mut Cheatcodes) -> Result {
24 let Self { json, key } = self;
25 check_json_key_exists(json, key)
26 }
27}
28
29impl Cheatcode for parseJson_0Call {
30 fn apply(&self, state: &mut Cheatcodes) -> Result {
31 let Self { json } = self;
32 parse_json(json, "$", state.struct_defs())
33 }
34}
35
36impl Cheatcode for parseJson_1Call {
37 fn apply(&self, state: &mut Cheatcodes) -> Result {
38 let Self { json, key } = self;
39 parse_json(json, key, state.struct_defs())
40 }
41}
42
43impl Cheatcode for parseJsonUintCall {
44 fn apply(&self, _state: &mut Cheatcodes) -> Result {
45 let Self { json, key } = self;
46 parse_json_coerce(json, key, &DynSolType::Uint(256))
47 }
48}
49
50impl Cheatcode for parseJsonUintArrayCall {
51 fn apply(&self, _state: &mut Cheatcodes) -> Result {
52 let Self { json, key } = self;
53 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256))))
54 }
55}
56
57impl Cheatcode for parseJsonIntCall {
58 fn apply(&self, _state: &mut Cheatcodes) -> Result {
59 let Self { json, key } = self;
60 parse_json_coerce(json, key, &DynSolType::Int(256))
61 }
62}
63
64impl Cheatcode for parseJsonIntArrayCall {
65 fn apply(&self, _state: &mut Cheatcodes) -> Result {
66 let Self { json, key } = self;
67 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256))))
68 }
69}
70
71impl Cheatcode for parseJsonBoolCall {
72 fn apply(&self, _state: &mut Cheatcodes) -> Result {
73 let Self { json, key } = self;
74 parse_json_coerce(json, key, &DynSolType::Bool)
75 }
76}
77
78impl Cheatcode for parseJsonBoolArrayCall {
79 fn apply(&self, _state: &mut Cheatcodes) -> Result {
80 let Self { json, key } = self;
81 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool)))
82 }
83}
84
85impl Cheatcode for parseJsonAddressCall {
86 fn apply(&self, _state: &mut Cheatcodes) -> Result {
87 let Self { json, key } = self;
88 parse_json_coerce(json, key, &DynSolType::Address)
89 }
90}
91
92impl Cheatcode for parseJsonAddressArrayCall {
93 fn apply(&self, _state: &mut Cheatcodes) -> Result {
94 let Self { json, key } = self;
95 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address)))
96 }
97}
98
99impl Cheatcode for parseJsonStringCall {
100 fn apply(&self, _state: &mut Cheatcodes) -> Result {
101 let Self { json, key } = self;
102 parse_json_coerce(json, key, &DynSolType::String)
103 }
104}
105
106impl Cheatcode for parseJsonStringArrayCall {
107 fn apply(&self, _state: &mut Cheatcodes) -> Result {
108 let Self { json, key } = self;
109 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String)))
110 }
111}
112
113impl Cheatcode for parseJsonBytesCall {
114 fn apply(&self, _state: &mut Cheatcodes) -> Result {
115 let Self { json, key } = self;
116 parse_json_coerce(json, key, &DynSolType::Bytes)
117 }
118}
119
120impl Cheatcode for parseJsonBytesArrayCall {
121 fn apply(&self, _state: &mut Cheatcodes) -> Result {
122 let Self { json, key } = self;
123 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes)))
124 }
125}
126
127impl Cheatcode for parseJsonBytes32Call {
128 fn apply(&self, _state: &mut Cheatcodes) -> Result {
129 let Self { json, key } = self;
130 parse_json_coerce(json, key, &DynSolType::FixedBytes(32))
131 }
132}
133
134impl Cheatcode for parseJsonBytes32ArrayCall {
135 fn apply(&self, _state: &mut Cheatcodes) -> Result {
136 let Self { json, key } = self;
137 parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32))))
138 }
139}
140
141impl Cheatcode for parseJsonType_0Call {
142 fn apply(&self, state: &mut Cheatcodes) -> Result {
143 let Self { json, typeDescription } = self;
144 parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?)
145 .map(|v| v.abi_encode())
146 }
147}
148
149impl Cheatcode for parseJsonType_1Call {
150 fn apply(&self, state: &mut Cheatcodes) -> Result {
151 let Self { json, key, typeDescription } = self;
152 parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?)
153 .map(|v| v.abi_encode())
154 }
155}
156
157impl Cheatcode for parseJsonTypeArrayCall {
158 fn apply(&self, state: &mut Cheatcodes) -> Result {
159 let Self { json, key, typeDescription } = self;
160 let ty = resolve_type(typeDescription, state.struct_defs())?;
161 parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
162 }
163}
164
165impl Cheatcode for parseJsonKeysCall {
166 fn apply(&self, _state: &mut Cheatcodes) -> Result {
167 let Self { json, key } = self;
168 parse_json_keys(json, key)
169 }
170}
171
172impl Cheatcode for serializeJsonCall {
173 fn apply(&self, state: &mut Cheatcodes) -> Result {
174 let Self { objectKey, value } = self;
175 *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?;
176 Ok(value.abi_encode())
177 }
178}
179
180impl Cheatcode for serializeBool_0Call {
181 fn apply(&self, state: &mut Cheatcodes) -> Result {
182 let Self { objectKey, valueKey, value } = self;
183 serialize_json(state, objectKey, valueKey, (*value).into())
184 }
185}
186
187impl Cheatcode for serializeUint_0Call {
188 fn apply(&self, state: &mut Cheatcodes) -> Result {
189 let Self { objectKey, valueKey, value } = self;
190 serialize_json(state, objectKey, valueKey, (*value).into())
191 }
192}
193
194impl Cheatcode for serializeInt_0Call {
195 fn apply(&self, state: &mut Cheatcodes) -> Result {
196 let Self { objectKey, valueKey, value } = self;
197 serialize_json(state, objectKey, valueKey, (*value).into())
198 }
199}
200
201impl Cheatcode for serializeAddress_0Call {
202 fn apply(&self, state: &mut Cheatcodes) -> Result {
203 let Self { objectKey, valueKey, value } = self;
204 serialize_json(state, objectKey, valueKey, (*value).into())
205 }
206}
207
208impl Cheatcode for serializeBytes32_0Call {
209 fn apply(&self, state: &mut Cheatcodes) -> Result {
210 let Self { objectKey, valueKey, value } = self;
211 serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32))
212 }
213}
214
215impl Cheatcode for serializeString_0Call {
216 fn apply(&self, state: &mut Cheatcodes) -> Result {
217 let Self { objectKey, valueKey, value } = self;
218 serialize_json(state, objectKey, valueKey, value.clone().into())
219 }
220}
221
222impl Cheatcode for serializeBytes_0Call {
223 fn apply(&self, state: &mut Cheatcodes) -> Result {
224 let Self { objectKey, valueKey, value } = self;
225 serialize_json(state, objectKey, valueKey, value.to_vec().into())
226 }
227}
228
229impl Cheatcode for serializeBool_1Call {
230 fn apply(&self, state: &mut Cheatcodes) -> Result {
231 let Self { objectKey, valueKey, values } = self;
232 serialize_json(
233 state,
234 objectKey,
235 valueKey,
236 DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()),
237 )
238 }
239}
240
241impl Cheatcode for serializeUint_1Call {
242 fn apply(&self, state: &mut Cheatcodes) -> Result {
243 let Self { objectKey, valueKey, values } = self;
244 serialize_json(
245 state,
246 objectKey,
247 valueKey,
248 DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()),
249 )
250 }
251}
252
253impl Cheatcode for serializeInt_1Call {
254 fn apply(&self, state: &mut Cheatcodes) -> Result {
255 let Self { objectKey, valueKey, values } = self;
256 serialize_json(
257 state,
258 objectKey,
259 valueKey,
260 DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()),
261 )
262 }
263}
264
265impl Cheatcode for serializeAddress_1Call {
266 fn apply(&self, state: &mut Cheatcodes) -> Result {
267 let Self { objectKey, valueKey, values } = self;
268 serialize_json(
269 state,
270 objectKey,
271 valueKey,
272 DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()),
273 )
274 }
275}
276
277impl Cheatcode for serializeBytes32_1Call {
278 fn apply(&self, state: &mut Cheatcodes) -> Result {
279 let Self { objectKey, valueKey, values } = self;
280 serialize_json(
281 state,
282 objectKey,
283 valueKey,
284 DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()),
285 )
286 }
287}
288
289impl Cheatcode for serializeString_1Call {
290 fn apply(&self, state: &mut Cheatcodes) -> Result {
291 let Self { objectKey, valueKey, values } = self;
292 serialize_json(
293 state,
294 objectKey,
295 valueKey,
296 DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()),
297 )
298 }
299}
300
301impl Cheatcode for serializeBytes_1Call {
302 fn apply(&self, state: &mut Cheatcodes) -> Result {
303 let Self { objectKey, valueKey, values } = self;
304 serialize_json(
305 state,
306 objectKey,
307 valueKey,
308 DynSolValue::Array(
309 values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(),
310 ),
311 )
312 }
313}
314
315impl Cheatcode for serializeJsonType_0Call {
316 fn apply(&self, state: &mut Cheatcodes) -> Result {
317 let Self { typeDescription, value } = self;
318 let ty = resolve_type(typeDescription, state.struct_defs())?;
319 let value = ty.abi_decode(value)?;
320 let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?;
321 Ok(value.to_string().abi_encode())
322 }
323}
324
325impl Cheatcode for serializeJsonType_1Call {
326 fn apply(&self, state: &mut Cheatcodes) -> Result {
327 let Self { objectKey, valueKey, typeDescription, value } = self;
328 let ty = resolve_type(typeDescription, state.struct_defs())?;
329 let value = ty.abi_decode(value)?;
330 serialize_json(state, objectKey, valueKey, value)
331 }
332}
333
334impl Cheatcode for serializeUintToHexCall {
335 fn apply(&self, state: &mut Cheatcodes) -> Result {
336 let Self { objectKey, valueKey, value } = self;
337 let hex = format!("0x{value:x}");
338 serialize_json(state, objectKey, valueKey, hex.into())
339 }
340}
341
342impl Cheatcode for writeJson_0Call {
343 fn apply(&self, state: &mut Cheatcodes) -> Result {
344 let Self { json, path } = self;
345 let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned()));
346 let json_string = serde_json::to_string_pretty(&json)?;
347 super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
348 }
349}
350
351impl Cheatcode for writeJson_1Call {
352 fn apply(&self, state: &mut Cheatcodes) -> Result {
353 let Self { json: value, path, valueKey } = self;
354
355 let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
358 let mut data = if data_path.exists() {
359 let data_string = fs::locked_read_to_string(&data_path)?;
360 serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string))
361 } else {
362 Value::Object(Default::default())
363 };
364 upsert_json_value(&mut data, value, valueKey)?;
365
366 let json_string = serde_json::to_string_pretty(&data)?;
368 super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
369 }
370}
371
372pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result {
373 let json = parse_json_str(json)?;
374 let values = select(&json, key)?;
375 let exists = !values.is_empty();
376 Ok(exists.abi_encode())
377}
378
379pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result {
380 let value = parse_json_str(json)?;
381 let selected = select(&value, path)?;
382 let sol = json_to_sol(defs, &selected)?;
383 Ok(encode(sol))
384}
385
386pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result {
387 let json = parse_json_str(json)?;
388 let [value] = select(&json, path)?[..] else {
389 bail!("path {path:?} must return exactly one JSON value");
390 };
391
392 parse_json_as(value, ty).map(|v| v.abi_encode())
393}
394
395pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result<DynSolValue> {
397 let to_string = |v: &Value| {
398 let mut s = v.to_string();
399 s.retain(|c: char| c != '"');
400 s
401 };
402
403 match (value, ty) {
404 (Value::Array(array), ty) => parse_json_array(array, ty),
405 (Value::Object(object), ty) => parse_json_map(object, ty),
406 (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
407 (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty),
408 _ => string::parse_value(&to_string(value), ty),
409 }
410}
411
412pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result<DynSolValue> {
413 match ty {
414 DynSolType::Tuple(types) => {
415 ensure!(array.len() == types.len(), "array length mismatch");
416 let values = array
417 .iter()
418 .zip(types)
419 .map(|(e, ty)| parse_json_as(e, ty))
420 .collect::<Result<Vec<_>>>()?;
421
422 Ok(DynSolValue::Tuple(values))
423 }
424 DynSolType::Array(inner) => {
425 let values =
426 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
427 Ok(DynSolValue::Array(values))
428 }
429 DynSolType::FixedArray(inner, len) => {
430 ensure!(array.len() == *len, "array length mismatch");
431 let values =
432 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
433 Ok(DynSolValue::FixedArray(values))
434 }
435 _ => bail!("expected {ty}, found array"),
436 }
437}
438
439pub(super) fn parse_json_map(map: &Map<String, Value>, ty: &DynSolType) -> Result<DynSolValue> {
440 let Some((name, fields, types)) = ty.as_custom_struct() else {
441 bail!("expected {ty}, found JSON object");
442 };
443
444 let mut values = Vec::with_capacity(fields.len());
445 for (field, ty) in fields.iter().zip(types.iter()) {
446 let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") };
447 values.push(parse_json_as(value, ty)?);
448 }
449
450 Ok(DynSolValue::CustomStruct {
451 name: name.to_string(),
452 prop_names: fields.to_vec(),
453 tuple: values,
454 })
455}
456
457pub(super) fn parse_json_keys(json: &str, key: &str) -> Result {
458 let json = parse_json_str(json)?;
459 let values = select(&json, key)?;
460 let [value] = values[..] else {
461 bail!("key {key:?} must return exactly one JSON object");
462 };
463 let Value::Object(object) = value else {
464 bail!("JSON value at {key:?} is not an object");
465 };
466 let keys = object.keys().collect::<Vec<_>>();
467 Ok(keys.abi_encode())
468}
469
470fn parse_json_str(json: &str) -> Result<Value> {
471 serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}"))
472}
473
474fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result<Vec<DynSolValue>> {
475 let mut sol = Vec::with_capacity(json.len());
476 for value in json {
477 sol.push(json_value_to_token(value, defs)?);
478 }
479 Ok(sol)
480}
481
482fn select<'a>(value: &'a Value, mut path: &str) -> Result<Vec<&'a Value>> {
483 if path == "." {
485 path = "$";
486 }
487 jsonpath_lib::select(value, &canonicalize_json_path(path))
489 .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string()))
490}
491
492fn encode(values: Vec<DynSolValue>) -> Vec<u8> {
493 let bytes = match &values[..] {
495 [] => Vec::new(),
496 [one] => one.abi_encode(),
497 _ => DynSolValue::Array(values).abi_encode(),
498 };
499 bytes.abi_encode()
500}
501
502pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
505 if !path.starts_with('$') { format!("${path}").into() } else { path.into() }
506}
507
508#[instrument(target = "cheatcodes", level = "trace", ret)]
515pub(super) fn json_value_to_token(
516 value: &Value,
517 defs: Option<&StructDefinitions>,
518) -> Result<DynSolValue> {
519 if let Some(defs) = defs {
520 _json_value_to_token(value, defs)
521 } else {
522 _json_value_to_token(value, &StructDefinitions::default())
523 }
524}
525
526fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result<DynSolValue> {
527 match value {
528 Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)),
529 Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)),
530 Value::Array(array) => array
531 .iter()
532 .map(|v| _json_value_to_token(v, defs))
533 .collect::<Result<_>>()
534 .map(DynSolValue::Array),
535 Value::Object(map) => {
536 let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect();
538 let matching_def = defs.values().find(|fields| {
539 fields.len() == keys.len()
540 && fields.iter().map(|(name, _)| name.as_str()).collect::<BTreeSet<_>>() == keys
541 });
542
543 if let Some(fields) = matching_def {
544 fields
546 .iter()
547 .map(|(name, _)| {
548 _json_value_to_token(map.get(name).unwrap(), defs)
550 })
551 .collect::<Result<_>>()
552 .map(DynSolValue::Tuple)
553 } else {
554 let ordered_object: BTreeMap<_, _> =
557 map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
558 ordered_object
559 .values()
560 .map(|value| _json_value_to_token(value, defs))
561 .collect::<Result<_>>()
562 .map(DynSolValue::Tuple)
563 }
564 }
565 Value::Number(number) => {
566 if let Some(f) = number.as_f64() {
567 if f.fract() == 0.0 {
570 let s = number.to_string();
576
577 if s.contains('e') {
582 let fallback_s = f.to_string();
588 if let Ok(n) = fallback_s.parse() {
589 return Ok(DynSolValue::Uint(n, 256));
590 }
591 if let Ok(n) = I256::from_dec_str(&fallback_s) {
592 return Ok(DynSolValue::Int(n, 256));
593 }
594 }
595
596 if let Ok(n) = s.parse() {
597 return Ok(DynSolValue::Uint(n, 256));
598 }
599 if let Ok(n) = s.parse() {
600 return Ok(DynSolValue::Int(n, 256));
601 }
602 }
603 }
604
605 Err(fmt_err!("unsupported JSON number: {number}"))
606 }
607 Value::String(string) => {
608 if let Some(mut val) = string.strip_prefix("0x") {
610 let s;
611 if val.len() == 39 {
612 return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into());
613 }
614 if !val.len().is_multiple_of(2) {
615 s = format!("0{val}");
616 val = &s[..];
617 }
618 if let Ok(bytes) = hex::decode(val) {
619 return Ok(match bytes.len() {
620 20 => DynSolValue::Address(Address::from_slice(&bytes)),
621 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32),
622 _ => DynSolValue::Bytes(bytes),
623 });
624 }
625 }
626
627 if let Ok(n) = string.parse::<I256>()
632 && i64::try_from(n).is_err()
633 {
634 return Ok(DynSolValue::Int(n, 256));
635 } else if let Ok(n) = string.parse::<U256>()
636 && u64::try_from(n).is_err()
637 {
638 return Ok(DynSolValue::Uint(n, 256));
639 }
640
641 Ok(DynSolValue::String(string.to_owned()))
643 }
644 }
645}
646
647fn serialize_json(
656 state: &mut Cheatcodes,
657 object_key: &str,
658 value_key: &str,
659 value: DynSolValue,
660) -> Result {
661 let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?;
662 let map = state.serialized_jsons.entry(object_key.into()).or_default();
663 map.insert(value_key.into(), value);
664 let stringified = serde_json::to_string(map).unwrap();
665 Ok(stringified.abi_encode())
666}
667
668pub(super) fn resolve_type(
670 type_description: &str,
671 struct_defs: Option<&StructDefinitions>,
672) -> Result<DynSolType> {
673 let ordered_ty = |ty| -> Result<DynSolType> {
674 if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) }
675 };
676
677 if let Ok(ty) = DynSolType::parse(type_description) {
678 return ordered_ty(ty);
679 };
680
681 if let Ok(encoded) = EncodeType::parse(type_description) {
682 let main_type = encoded.types[0].type_name;
683 let mut resolver = Resolver::default();
684 for t in &encoded.types {
685 resolver.ingest(t.to_owned());
686 }
687
688 return ordered_ty(resolver.resolve(main_type)?);
690 }
691
692 bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string")
693}
694
695pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
708 let canonical_key = canonicalize_json_path(key);
710 let parts: Vec<&str> = canonical_key
711 .strip_prefix("$.")
712 .unwrap_or(key)
713 .split('.')
714 .filter(|s| !s.is_empty())
715 .collect();
716
717 if parts.is_empty() {
718 return Err(fmt_err!("'valueKey' cannot be empty or just '$'"));
719 }
720
721 if let Some((key_to_insert, path_to_parent)) = parts.split_last() {
724 let mut current_level = data;
725
726 for segment in path_to_parent {
727 if !current_level.is_object() {
728 return Err(fmt_err!("path segment '{segment}' does not resolve to an object."));
729 }
730 current_level = current_level
731 .as_object_mut()
732 .unwrap()
733 .entry(segment.to_string())
734 .or_insert(Value::Object(Map::new()));
735 }
736
737 if let Some(parent_obj) = current_level.as_object_mut() {
739 parent_obj.insert(
740 key_to_insert.to_string(),
741 serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())),
742 );
743 } else {
744 return Err(fmt_err!("final destination is not an object, cannot insert key."));
745 }
746 }
747
748 Ok(())
749}
750
751fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result<DynSolType> {
757 match ty {
758 DynSolType::CustomStruct { name, prop_names, tuple } => {
759 if let Some(def) = struct_defs.get(&name)? {
760 let type_map: std::collections::HashMap<String, DynSolType> =
762 prop_names.into_iter().zip(tuple).collect();
763
764 let mut sorted_props = Vec::with_capacity(def.len());
765 let mut sorted_tuple = Vec::with_capacity(def.len());
766 for (field_name, _) in def {
767 sorted_props.push(field_name.clone());
768 if let Some(field_ty) = type_map.get(field_name) {
769 sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?);
770 } else {
771 bail!(
772 "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'"
773 );
774 }
775 }
776 Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple })
777 } else {
778 let new_tuple = tuple
781 .into_iter()
782 .map(|t| reorder_type(t, struct_defs))
783 .collect::<Result<Vec<_>>>()?;
784 Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple })
785 }
786 }
787 DynSolType::Array(inner) => {
788 Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?)))
789 }
790 DynSolType::FixedArray(inner, len) => {
791 Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len))
792 }
793 DynSolType::Tuple(inner) => Ok(DynSolType::Tuple(
794 inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::<Result<Vec<_>>>()?,
795 )),
796 _ => Ok(ty),
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use alloy_primitives::FixedBytes;
804 use foundry_common::fmt::{TypeDefMap, serialize_value_as_json};
805 use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
806 use std::collections::HashSet;
807
808 fn valid_value(value: &DynSolValue) -> bool {
809 (match value {
810 DynSolValue::String(s) if s == "{}" => false,
811
812 DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false,
813
814 DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value),
815 _ => true,
816 }) && value.as_type().is_some()
817 }
818
819 fn fixup_guessable(value: DynSolValue) -> DynSolValue {
824 match value {
825 DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => {
826 if let Some(DynSolValue::Bytes(_)) = v.first() {
827 v.retain(|v| {
828 let len = v.as_bytes().unwrap().len();
829 len != 32 && len != 20
830 })
831 }
832 DynSolValue::Array(v.into_iter().map(fixup_guessable).collect())
833 }
834 DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32),
835 DynSolValue::Bytes(v) if v.len() == 32 => {
836 DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32)
837 }
838 DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)),
839 _ => value,
840 }
841 }
842
843 fn guessable_types() -> impl proptest::strategy::Strategy<Value = DynSolValue> {
844 any::<DynSolValue>().prop_map(fixup_guessable).prop_filter("invalid value", valid_value)
845 }
846
847 fn custom_struct_strategy() -> impl Strategy<Value = (StructDefinitions, DynSolValue)> {
850 let field_name_strat = "[a-z]{4,12}";
852 let field_value_strat = prop_oneof![
853 any::<bool>().prop_map(DynSolValue::Bool),
854 any::<u32>().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)),
855 any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address),
856 any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)),
857 ".*".prop_map(DynSolValue::String),
858 ];
859
860 let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8)
862 .prop_map(|fields| {
863 let mut unique_fields = Vec::with_capacity(fields.len());
864 let mut seen_names = HashSet::new();
865 for (name, value) in fields {
866 if seen_names.insert(name.clone()) {
867 unique_fields.push((name, value));
868 }
869 }
870 unique_fields
871 });
872
873 ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| {
875 let (prop_names, tuple): (Vec<String>, Vec<DynSolValue>) =
876 fields.clone().into_iter().unzip();
877 let def_fields: Vec<(String, String)> = fields
878 .iter()
879 .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string()))
880 .collect();
881 let mut defs_map = TypeDefMap::default();
882 defs_map.insert(struct_name.clone(), def_fields);
883 (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple })
884 })
885 }
886
887 proptest::proptest! {
889 #[test]
890 fn test_json_roundtrip_guessed(v in guessable_types()) {
891 let json = serialize_value_as_json(v.clone(), None).unwrap();
892 let value = json_value_to_token(&json, None).unwrap();
893
894 let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap();
896 assert_eq!(decoded, v);
897 }
898
899 #[test]
900 fn test_json_roundtrip(v in any::<DynSolValue>().prop_filter("filter out values without type", |v| v.as_type().is_some())) {
901 let json = serialize_value_as_json(v.clone(), None).unwrap();
902 let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap();
903 assert_eq!(value, v);
904 }
905
906 #[test]
907 fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) {
908 let json = serialize_value_as_json(v.clone(), Some(&struct_defs)).unwrap();
909 let sol_type = v.as_type().unwrap();
910 let parsed_value = parse_json_as(&json, &sol_type).unwrap();
911 assert_eq!(parsed_value, v);
912 }
913 }
914
915 #[test]
916 fn test_resolve_type_with_definitions() -> Result<()> {
917 let mut struct_defs = TypeDefMap::new();
919 struct_defs.insert(
920 "Apple".to_string(),
921 vec![
922 ("color".to_string(), "string".to_string()),
923 ("sweetness".to_string(), "uint8".to_string()),
924 ("sourness".to_string(), "uint8".to_string()),
925 ],
926 );
927 struct_defs.insert(
928 "FruitStall".to_string(),
929 vec![
930 ("name".to_string(), "string".to_string()),
931 ("apples".to_string(), "Apple[]".to_string()),
932 ],
933 );
934
935 let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)";
937
938 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
940 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
941 assert_eq!(name, "FruitStall");
942 assert_eq!(prop_names, vec!["name", "apples"]);
943 assert_eq!(tuple.len(), 2);
944 assert_eq!(tuple[0], DynSolType::String);
945
946 if let DynSolType::Array(apple_ty_boxed) = &tuple[1]
947 && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed
948 {
949 assert_eq!(*name, "Apple");
950 assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]);
952 assert_eq!(
953 *tuple,
954 vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)]
955 );
956
957 return Ok(());
958 }
959 }
960 panic!("Expected FruitStall and Apple to be CustomStruct");
961 }
962
963 #[test]
964 fn test_resolve_type_without_definitions() -> Result<()> {
965 let ty_desc = "Person(bool active,uint256 age,string name)";
967
968 let ty = resolve_type(ty_desc, None).unwrap();
971 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
972 assert_eq!(name, "Person");
973 assert_eq!(prop_names, vec!["active", "age", "name"]);
974 assert_eq!(tuple.len(), 3);
975 assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]);
976 return Ok(());
977 }
978 panic!("Expected Person to be CustomStruct");
979 }
980
981 #[test]
982 fn test_resolve_type_for_array_of_structs() -> Result<()> {
983 let mut struct_defs = TypeDefMap::new();
985 struct_defs.insert(
986 "Item".to_string(),
987 vec![
988 ("name".to_string(), "string".to_string()),
989 ("price".to_string(), "uint256".to_string()),
990 ("id".to_string(), "uint256".to_string()),
991 ],
992 );
993
994 let ty_desc = "Item(uint256 id,string name,uint256 price)";
996
997 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
999 let array_ty = DynSolType::Array(Box::new(ty));
1000 if let DynSolType::Array(item_ty) = array_ty
1001 && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty
1002 {
1003 assert_eq!(name, "Item");
1004 assert_eq!(prop_names, vec!["name", "price", "id"]);
1005 assert_eq!(
1006 tuple,
1007 vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)]
1008 );
1009 return Ok(());
1010 }
1011 panic!("Expected CustomStruct in array");
1012 }
1013
1014 #[test]
1015 fn test_parse_json_missing_field() {
1016 let mut struct_defs = TypeDefMap::new();
1018 struct_defs.insert(
1019 "Person".to_string(),
1020 vec![
1021 ("name".to_string(), "string".to_string()),
1022 ("age".to_string(), "uint256".to_string()),
1023 ],
1024 );
1025
1026 let json_str = r#"{ "name": "Alice" }"#;
1028
1029 let type_description = "Person(uint256 age,string name)";
1031 let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1032
1033 let json_value: Value = serde_json::from_str(json_str).unwrap();
1035 let result = parse_json_as(&json_value, &ty);
1036
1037 assert!(result.is_err());
1039 assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object"));
1040 }
1041
1042 #[test]
1043 fn test_serialize_json_with_struct_def_order() {
1044 let mut struct_defs = TypeDefMap::new();
1046 struct_defs.insert(
1047 "Item".to_string(),
1048 vec![
1049 ("name".to_string(), "string".to_string()),
1050 ("id".to_string(), "uint256".to_string()),
1051 ("active".to_string(), "bool".to_string()),
1052 ],
1053 );
1054
1055 let item_struct = DynSolValue::CustomStruct {
1057 name: "Item".to_string(),
1058 prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()],
1059 tuple: vec![
1060 DynSolValue::String("Test Item".to_string()),
1061 DynSolValue::Uint(U256::from(123), 256),
1062 DynSolValue::Bool(true),
1063 ],
1064 };
1065
1066 let json_value = serialize_value_as_json(item_struct, Some(&struct_defs.into())).unwrap();
1068 let json_string = serde_json::to_string(&json_value).unwrap();
1069 assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#);
1070 }
1071
1072 #[test]
1073 fn test_json_full_cycle_typed_with_struct_defs() {
1074 let mut struct_defs = TypeDefMap::new();
1076 struct_defs.insert(
1077 "Wallet".to_string(),
1078 vec![
1079 ("owner".to_string(), "address".to_string()),
1080 ("balance".to_string(), "uint256".to_string()),
1081 ("id".to_string(), "bytes32".to_string()),
1082 ],
1083 );
1084
1085 let owner_address = Address::from([1; 20]);
1087 let wallet_id = B256::from([2; 32]);
1088 let original_wallet = DynSolValue::CustomStruct {
1089 name: "Wallet".to_string(),
1090 prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()],
1091 tuple: vec![
1092 DynSolValue::Address(owner_address),
1093 DynSolValue::Uint(U256::from(5000), 256),
1094 DynSolValue::FixedBytes(wallet_id, 32),
1095 ],
1096 };
1097
1098 let json_value =
1100 serialize_value_as_json(original_wallet.clone(), Some(&struct_defs.clone().into()))
1101 .unwrap();
1102 let json_string = serde_json::to_string(&json_value).unwrap();
1103 assert_eq!(
1104 json_string,
1105 format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#)
1106 );
1107
1108 let type_description = "Wallet(uint256 balance,bytes32 id,address owner)";
1110 let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1111
1112 let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap();
1115 assert_eq!(parsed_value, original_wallet);
1116 }
1117}