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)?;
357 let data_string = fs::locked_read_to_string(&data_path)?;
358 let mut data =
359 serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string));
360 upsert_json_value(&mut data, value, valueKey)?;
361
362 let json_string = serde_json::to_string_pretty(&data)?;
364 super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
365 }
366}
367
368pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result {
369 let json = parse_json_str(json)?;
370 let values = select(&json, key)?;
371 let exists = !values.is_empty();
372 Ok(exists.abi_encode())
373}
374
375pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result {
376 let value = parse_json_str(json)?;
377 let selected = select(&value, path)?;
378 let sol = json_to_sol(defs, &selected)?;
379 Ok(encode(sol))
380}
381
382pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result {
383 let json = parse_json_str(json)?;
384 let [value] = select(&json, path)?[..] else {
385 bail!("path {path:?} must return exactly one JSON value");
386 };
387
388 parse_json_as(value, ty).map(|v| v.abi_encode())
389}
390
391pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result<DynSolValue> {
393 let to_string = |v: &Value| {
394 let mut s = v.to_string();
395 s.retain(|c: char| c != '"');
396 s
397 };
398
399 match (value, ty) {
400 (Value::Array(array), ty) => parse_json_array(array, ty),
401 (Value::Object(object), ty) => parse_json_map(object, ty),
402 (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
403 (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty),
404 _ => string::parse_value(&to_string(value), ty),
405 }
406}
407
408pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result<DynSolValue> {
409 match ty {
410 DynSolType::Tuple(types) => {
411 ensure!(array.len() == types.len(), "array length mismatch");
412 let values = array
413 .iter()
414 .zip(types)
415 .map(|(e, ty)| parse_json_as(e, ty))
416 .collect::<Result<Vec<_>>>()?;
417
418 Ok(DynSolValue::Tuple(values))
419 }
420 DynSolType::Array(inner) => {
421 let values =
422 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
423 Ok(DynSolValue::Array(values))
424 }
425 DynSolType::FixedArray(inner, len) => {
426 ensure!(array.len() == *len, "array length mismatch");
427 let values =
428 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
429 Ok(DynSolValue::FixedArray(values))
430 }
431 _ => bail!("expected {ty}, found array"),
432 }
433}
434
435pub(super) fn parse_json_map(map: &Map<String, Value>, ty: &DynSolType) -> Result<DynSolValue> {
436 let Some((name, fields, types)) = ty.as_custom_struct() else {
437 bail!("expected {ty}, found JSON object");
438 };
439
440 let mut values = Vec::with_capacity(fields.len());
441 for (field, ty) in fields.iter().zip(types.iter()) {
442 let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") };
443 values.push(parse_json_as(value, ty)?);
444 }
445
446 Ok(DynSolValue::CustomStruct {
447 name: name.to_string(),
448 prop_names: fields.to_vec(),
449 tuple: values,
450 })
451}
452
453pub(super) fn parse_json_keys(json: &str, key: &str) -> Result {
454 let json = parse_json_str(json)?;
455 let values = select(&json, key)?;
456 let [value] = values[..] else {
457 bail!("key {key:?} must return exactly one JSON object");
458 };
459 let Value::Object(object) = value else {
460 bail!("JSON value at {key:?} is not an object");
461 };
462 let keys = object.keys().collect::<Vec<_>>();
463 Ok(keys.abi_encode())
464}
465
466fn parse_json_str(json: &str) -> Result<Value> {
467 serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}"))
468}
469
470fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result<Vec<DynSolValue>> {
471 let mut sol = Vec::with_capacity(json.len());
472 for value in json {
473 sol.push(json_value_to_token(value, defs)?);
474 }
475 Ok(sol)
476}
477
478fn select<'a>(value: &'a Value, mut path: &str) -> Result<Vec<&'a Value>> {
479 if path == "." {
481 path = "$";
482 }
483 jsonpath_lib::select(value, &canonicalize_json_path(path))
485 .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string()))
486}
487
488fn encode(values: Vec<DynSolValue>) -> Vec<u8> {
489 let bytes = match &values[..] {
491 [] => Vec::new(),
492 [one] => one.abi_encode(),
493 _ => DynSolValue::Array(values).abi_encode(),
494 };
495 bytes.abi_encode()
496}
497
498pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
501 if !path.starts_with('$') { format!("${path}").into() } else { path.into() }
502}
503
504#[instrument(target = "cheatcodes", level = "trace", ret)]
511pub(super) fn json_value_to_token(
512 value: &Value,
513 defs: Option<&StructDefinitions>,
514) -> Result<DynSolValue> {
515 if let Some(defs) = defs {
516 _json_value_to_token(value, defs)
517 } else {
518 _json_value_to_token(value, &StructDefinitions::default())
519 }
520}
521
522fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result<DynSolValue> {
523 match value {
524 Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)),
525 Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)),
526 Value::Array(array) => array
527 .iter()
528 .map(|v| _json_value_to_token(v, defs))
529 .collect::<Result<_>>()
530 .map(DynSolValue::Array),
531 Value::Object(map) => {
532 let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect();
534 let matching_def = defs.values().find(|fields| {
535 fields.len() == keys.len()
536 && fields.iter().map(|(name, _)| name.as_str()).collect::<BTreeSet<_>>() == keys
537 });
538
539 if let Some(fields) = matching_def {
540 fields
542 .iter()
543 .map(|(name, _)| {
544 _json_value_to_token(map.get(name).unwrap(), defs)
546 })
547 .collect::<Result<_>>()
548 .map(DynSolValue::Tuple)
549 } else {
550 let ordered_object: BTreeMap<_, _> =
553 map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
554 ordered_object
555 .values()
556 .map(|value| _json_value_to_token(value, defs))
557 .collect::<Result<_>>()
558 .map(DynSolValue::Tuple)
559 }
560 }
561 Value::Number(number) => {
562 if let Some(f) = number.as_f64() {
563 if f.fract() == 0.0 {
566 let s = number.to_string();
572
573 if s.contains('e') {
578 let fallback_s = f.to_string();
584 if let Ok(n) = fallback_s.parse() {
585 return Ok(DynSolValue::Uint(n, 256));
586 }
587 if let Ok(n) = I256::from_dec_str(&fallback_s) {
588 return Ok(DynSolValue::Int(n, 256));
589 }
590 }
591
592 if let Ok(n) = s.parse() {
593 return Ok(DynSolValue::Uint(n, 256));
594 }
595 if let Ok(n) = s.parse() {
596 return Ok(DynSolValue::Int(n, 256));
597 }
598 }
599 }
600
601 Err(fmt_err!("unsupported JSON number: {number}"))
602 }
603 Value::String(string) => {
604 if let Some(mut val) = string.strip_prefix("0x") {
606 let s;
607 if val.len() == 39 {
608 return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into());
609 }
610 if !val.len().is_multiple_of(2) {
611 s = format!("0{val}");
612 val = &s[..];
613 }
614 if let Ok(bytes) = hex::decode(val) {
615 return Ok(match bytes.len() {
616 20 => DynSolValue::Address(Address::from_slice(&bytes)),
617 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32),
618 _ => DynSolValue::Bytes(bytes),
619 });
620 }
621 }
622
623 if let Ok(n) = string.parse::<I256>()
628 && i64::try_from(n).is_err()
629 {
630 return Ok(DynSolValue::Int(n, 256));
631 } else if let Ok(n) = string.parse::<U256>()
632 && u64::try_from(n).is_err()
633 {
634 return Ok(DynSolValue::Uint(n, 256));
635 }
636
637 Ok(DynSolValue::String(string.to_owned()))
639 }
640 }
641}
642
643fn serialize_json(
652 state: &mut Cheatcodes,
653 object_key: &str,
654 value_key: &str,
655 value: DynSolValue,
656) -> Result {
657 let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?;
658 let map = state.serialized_jsons.entry(object_key.into()).or_default();
659 map.insert(value_key.into(), value);
660 let stringified = serde_json::to_string(map).unwrap();
661 Ok(stringified.abi_encode())
662}
663
664pub(super) fn resolve_type(
666 type_description: &str,
667 struct_defs: Option<&StructDefinitions>,
668) -> Result<DynSolType> {
669 let ordered_ty = |ty| -> Result<DynSolType> {
670 if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) }
671 };
672
673 if let Ok(ty) = DynSolType::parse(type_description) {
674 return ordered_ty(ty);
675 };
676
677 if let Ok(encoded) = EncodeType::parse(type_description) {
678 let main_type = encoded.types[0].type_name;
679 let mut resolver = Resolver::default();
680 for t in &encoded.types {
681 resolver.ingest(t.to_owned());
682 }
683
684 return ordered_ty(resolver.resolve(main_type)?);
686 }
687
688 bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string")
689}
690
691pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
704 let canonical_key = canonicalize_json_path(key);
706 let parts: Vec<&str> = canonical_key
707 .strip_prefix("$.")
708 .unwrap_or(key)
709 .split('.')
710 .filter(|s| !s.is_empty())
711 .collect();
712
713 if parts.is_empty() {
714 return Err(fmt_err!("'valueKey' cannot be empty or just '$'"));
715 }
716
717 if let Some((key_to_insert, path_to_parent)) = parts.split_last() {
720 let mut current_level = data;
721
722 for segment in path_to_parent {
723 if !current_level.is_object() {
724 return Err(fmt_err!("path segment '{segment}' does not resolve to an object."));
725 }
726 current_level = current_level
727 .as_object_mut()
728 .unwrap()
729 .entry(segment.to_string())
730 .or_insert(Value::Object(Map::new()));
731 }
732
733 if let Some(parent_obj) = current_level.as_object_mut() {
735 parent_obj.insert(
736 key_to_insert.to_string(),
737 serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())),
738 );
739 } else {
740 return Err(fmt_err!("final destination is not an object, cannot insert key."));
741 }
742 }
743
744 Ok(())
745}
746
747fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result<DynSolType> {
753 match ty {
754 DynSolType::CustomStruct { name, prop_names, tuple } => {
755 if let Some(def) = struct_defs.get(&name)? {
756 let type_map: std::collections::HashMap<String, DynSolType> =
758 prop_names.into_iter().zip(tuple).collect();
759
760 let mut sorted_props = Vec::with_capacity(def.len());
761 let mut sorted_tuple = Vec::with_capacity(def.len());
762 for (field_name, _) in def {
763 sorted_props.push(field_name.clone());
764 if let Some(field_ty) = type_map.get(field_name) {
765 sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?);
766 } else {
767 bail!(
768 "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'"
769 );
770 }
771 }
772 Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple })
773 } else {
774 let new_tuple = tuple
777 .into_iter()
778 .map(|t| reorder_type(t, struct_defs))
779 .collect::<Result<Vec<_>>>()?;
780 Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple })
781 }
782 }
783 DynSolType::Array(inner) => {
784 Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?)))
785 }
786 DynSolType::FixedArray(inner, len) => {
787 Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len))
788 }
789 DynSolType::Tuple(inner) => Ok(DynSolType::Tuple(
790 inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::<Result<Vec<_>>>()?,
791 )),
792 _ => Ok(ty),
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799 use alloy_primitives::FixedBytes;
800 use foundry_common::fmt::{TypeDefMap, serialize_value_as_json};
801 use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
802 use std::collections::HashSet;
803
804 fn contains_tuple(value: &DynSolValue) -> bool {
805 match value {
806 DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => true,
807 DynSolValue::Array(v) | DynSolValue::FixedArray(v) => {
808 v.first().is_some_and(contains_tuple)
809 }
810 _ => false,
811 }
812 }
813
814 fn fixup_guessable(value: DynSolValue) -> DynSolValue {
819 match value {
820 DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => {
821 if let Some(DynSolValue::Bytes(_)) = v.first() {
822 v.retain(|v| {
823 let len = v.as_bytes().unwrap().len();
824 len != 32 && len != 20
825 })
826 }
827 DynSolValue::Array(v.into_iter().map(fixup_guessable).collect())
828 }
829 DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32),
830 DynSolValue::Bytes(v) if v.len() == 32 => {
831 DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32)
832 }
833 DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)),
834 _ => value,
835 }
836 }
837
838 fn guessable_types() -> impl proptest::strategy::Strategy<Value = DynSolValue> {
839 any::<DynSolValue>()
840 .prop_map(fixup_guessable)
841 .prop_filter("tuples are not supported", |v| !contains_tuple(v))
842 .prop_filter("filter out values without type", |v| v.as_type().is_some())
843 }
844
845 fn custom_struct_strategy() -> impl Strategy<Value = (StructDefinitions, DynSolValue)> {
848 let field_name_strat = "[a-z]{4,12}";
850 let field_value_strat = prop_oneof![
851 any::<bool>().prop_map(DynSolValue::Bool),
852 any::<u32>().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)),
853 any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address),
854 any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)),
855 ".*".prop_map(DynSolValue::String),
856 ];
857
858 let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8)
860 .prop_map(|fields| {
861 let mut unique_fields = Vec::with_capacity(fields.len());
862 let mut seen_names = HashSet::new();
863 for (name, value) in fields {
864 if seen_names.insert(name.clone()) {
865 unique_fields.push((name, value));
866 }
867 }
868 unique_fields
869 });
870
871 ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| {
873 let (prop_names, tuple): (Vec<String>, Vec<DynSolValue>) =
874 fields.clone().into_iter().unzip();
875 let def_fields: Vec<(String, String)> = fields
876 .iter()
877 .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string()))
878 .collect();
879 let mut defs_map = TypeDefMap::default();
880 defs_map.insert(struct_name.clone(), def_fields);
881 (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple })
882 })
883 }
884
885 proptest::proptest! {
887 #[test]
888 fn test_json_roundtrip_guessed(v in guessable_types()) {
889 let json = serialize_value_as_json(v.clone(), None).unwrap();
890 let value = json_value_to_token(&json, None).unwrap();
891
892 let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap();
894 assert_eq!(decoded, v);
895 }
896
897 #[test]
898 fn test_json_roundtrip(v in any::<DynSolValue>().prop_filter("filter out values without type", |v| v.as_type().is_some())) {
899 let json = serialize_value_as_json(v.clone(), None).unwrap();
900 let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap();
901 assert_eq!(value, v);
902 }
903
904 #[test]
905 fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) {
906 let json = serialize_value_as_json(v.clone(), Some(&struct_defs)).unwrap();
907 let sol_type = v.as_type().unwrap();
908 let parsed_value = parse_json_as(&json, &sol_type).unwrap();
909 assert_eq!(parsed_value, v);
910 }
911 }
912
913 #[test]
914 fn test_resolve_type_with_definitions() -> Result<()> {
915 let mut struct_defs = TypeDefMap::new();
917 struct_defs.insert(
918 "Apple".to_string(),
919 vec![
920 ("color".to_string(), "string".to_string()),
921 ("sweetness".to_string(), "uint8".to_string()),
922 ("sourness".to_string(), "uint8".to_string()),
923 ],
924 );
925 struct_defs.insert(
926 "FruitStall".to_string(),
927 vec![
928 ("name".to_string(), "string".to_string()),
929 ("apples".to_string(), "Apple[]".to_string()),
930 ],
931 );
932
933 let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)";
935
936 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
938 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
939 assert_eq!(name, "FruitStall");
940 assert_eq!(prop_names, vec!["name", "apples"]);
941 assert_eq!(tuple.len(), 2);
942 assert_eq!(tuple[0], DynSolType::String);
943
944 if let DynSolType::Array(apple_ty_boxed) = &tuple[1]
945 && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed
946 {
947 assert_eq!(*name, "Apple");
948 assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]);
950 assert_eq!(
951 *tuple,
952 vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)]
953 );
954
955 return Ok(());
956 }
957 }
958 panic!("Expected FruitStall and Apple to be CustomStruct");
959 }
960
961 #[test]
962 fn test_resolve_type_without_definitions() -> Result<()> {
963 let ty_desc = "Person(bool active,uint256 age,string name)";
965
966 let ty = resolve_type(ty_desc, None).unwrap();
969 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
970 assert_eq!(name, "Person");
971 assert_eq!(prop_names, vec!["active", "age", "name"]);
972 assert_eq!(tuple.len(), 3);
973 assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]);
974 return Ok(());
975 }
976 panic!("Expected Person to be CustomStruct");
977 }
978
979 #[test]
980 fn test_resolve_type_for_array_of_structs() -> Result<()> {
981 let mut struct_defs = TypeDefMap::new();
983 struct_defs.insert(
984 "Item".to_string(),
985 vec![
986 ("name".to_string(), "string".to_string()),
987 ("price".to_string(), "uint256".to_string()),
988 ("id".to_string(), "uint256".to_string()),
989 ],
990 );
991
992 let ty_desc = "Item(uint256 id,string name,uint256 price)";
994
995 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
997 let array_ty = DynSolType::Array(Box::new(ty));
998 if let DynSolType::Array(item_ty) = array_ty
999 && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty
1000 {
1001 assert_eq!(name, "Item");
1002 assert_eq!(prop_names, vec!["name", "price", "id"]);
1003 assert_eq!(
1004 tuple,
1005 vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)]
1006 );
1007 return Ok(());
1008 }
1009 panic!("Expected CustomStruct in array");
1010 }
1011
1012 #[test]
1013 fn test_parse_json_missing_field() {
1014 let mut struct_defs = TypeDefMap::new();
1016 struct_defs.insert(
1017 "Person".to_string(),
1018 vec![
1019 ("name".to_string(), "string".to_string()),
1020 ("age".to_string(), "uint256".to_string()),
1021 ],
1022 );
1023
1024 let json_str = r#"{ "name": "Alice" }"#;
1026
1027 let type_description = "Person(uint256 age,string name)";
1029 let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1030
1031 let json_value: Value = serde_json::from_str(json_str).unwrap();
1033 let result = parse_json_as(&json_value, &ty);
1034
1035 assert!(result.is_err());
1037 assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object"));
1038 }
1039
1040 #[test]
1041 fn test_serialize_json_with_struct_def_order() {
1042 let mut struct_defs = TypeDefMap::new();
1044 struct_defs.insert(
1045 "Item".to_string(),
1046 vec![
1047 ("name".to_string(), "string".to_string()),
1048 ("id".to_string(), "uint256".to_string()),
1049 ("active".to_string(), "bool".to_string()),
1050 ],
1051 );
1052
1053 let item_struct = DynSolValue::CustomStruct {
1055 name: "Item".to_string(),
1056 prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()],
1057 tuple: vec![
1058 DynSolValue::String("Test Item".to_string()),
1059 DynSolValue::Uint(U256::from(123), 256),
1060 DynSolValue::Bool(true),
1061 ],
1062 };
1063
1064 let json_value = serialize_value_as_json(item_struct, Some(&struct_defs.into())).unwrap();
1066 let json_string = serde_json::to_string(&json_value).unwrap();
1067 assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#);
1068 }
1069
1070 #[test]
1071 fn test_json_full_cycle_typed_with_struct_defs() {
1072 let mut struct_defs = TypeDefMap::new();
1074 struct_defs.insert(
1075 "Wallet".to_string(),
1076 vec![
1077 ("owner".to_string(), "address".to_string()),
1078 ("balance".to_string(), "uint256".to_string()),
1079 ("id".to_string(), "bytes32".to_string()),
1080 ],
1081 );
1082
1083 let owner_address = Address::from([1; 20]);
1085 let wallet_id = B256::from([2; 32]);
1086 let original_wallet = DynSolValue::CustomStruct {
1087 name: "Wallet".to_string(),
1088 prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()],
1089 tuple: vec![
1090 DynSolValue::Address(owner_address),
1091 DynSolValue::Uint(U256::from(5000), 256),
1092 DynSolValue::FixedBytes(wallet_id, 32),
1093 ],
1094 };
1095
1096 let json_value =
1098 serialize_value_as_json(original_wallet.clone(), Some(&struct_defs.clone().into()))
1099 .unwrap();
1100 let json_string = serde_json::to_string(&json_value).unwrap();
1101 assert_eq!(
1102 json_string,
1103 format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#)
1104 );
1105
1106 let type_description = "Wallet(uint256 balance,bytes32 id,address owner)";
1108 let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1109
1110 let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap();
1113 assert_eq!(parsed_value, original_wallet);
1114 }
1115}