1use crate::{Cheatcode, CheatcodesCore, 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_context_free(&self) -> Result {
17 let Self { json, key } = self;
18 check_json_key_exists(json, key)
19 }
20}
21
22impl Cheatcode for keyExistsJsonCall {
23 fn apply_context_free(&self) -> 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_context(&self, state: &mut CheatcodesCore) -> Result {
31 let Self { json } = self;
32 parse_json(json, "$", state.struct_defs())
33 }
34}
35
36impl Cheatcode for parseJson_1Call {
37 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
38 let Self { json, key } = self;
39 parse_json(json, key, state.struct_defs())
40 }
41}
42
43macro_rules! impl_parse_json {
44 ($call:ident, $call_with_default:ident, $ty:expr) => {
45 impl Cheatcode for $call {
46 fn apply_context_free(&self) -> Result {
47 let Self { json, key } = self;
48 parse_json_coerce(json, key, &$ty)
49 }
50 }
51
52 impl Cheatcode for $call_with_default {
53 fn apply_context_free(&self) -> Result {
54 let Self { json, key, defaultValue } = self;
55 parse_json_coerce_default(json, key, &$ty, defaultValue)
56 }
57 }
58 };
59}
60
61impl_parse_json!(parseJsonUint_0Call, parseJsonUint_1Call, DynSolType::Uint(256));
62impl_parse_json!(
63 parseJsonUintArray_0Call,
64 parseJsonUintArray_1Call,
65 DynSolType::Array(Box::new(DynSolType::Uint(256)))
66);
67impl_parse_json!(parseJsonInt_0Call, parseJsonInt_1Call, DynSolType::Int(256));
68impl_parse_json!(
69 parseJsonIntArray_0Call,
70 parseJsonIntArray_1Call,
71 DynSolType::Array(Box::new(DynSolType::Int(256)))
72);
73impl_parse_json!(parseJsonBool_0Call, parseJsonBool_1Call, DynSolType::Bool);
74impl_parse_json!(
75 parseJsonBoolArray_0Call,
76 parseJsonBoolArray_1Call,
77 DynSolType::Array(Box::new(DynSolType::Bool))
78);
79impl_parse_json!(parseJsonAddress_0Call, parseJsonAddress_1Call, DynSolType::Address);
80impl_parse_json!(
81 parseJsonAddressArray_0Call,
82 parseJsonAddressArray_1Call,
83 DynSolType::Array(Box::new(DynSolType::Address))
84);
85impl_parse_json!(parseJsonString_0Call, parseJsonString_1Call, DynSolType::String);
86impl_parse_json!(
87 parseJsonStringArray_0Call,
88 parseJsonStringArray_1Call,
89 DynSolType::Array(Box::new(DynSolType::String))
90);
91
92impl Cheatcode for parseJsonArrayLengthCall {
93 fn apply_context_free(&self) -> Result {
94 let Self { json, key } = self;
95 parse_json_array_length(json, key)
96 }
97}
98
99impl_parse_json!(parseJsonBytes_0Call, parseJsonBytes_1Call, DynSolType::Bytes);
100impl_parse_json!(
101 parseJsonBytesArray_0Call,
102 parseJsonBytesArray_1Call,
103 DynSolType::Array(Box::new(DynSolType::Bytes))
104);
105impl_parse_json!(parseJsonBytes32_0Call, parseJsonBytes32_1Call, DynSolType::FixedBytes(32));
106impl_parse_json!(
107 parseJsonBytes32Array_0Call,
108 parseJsonBytes32Array_1Call,
109 DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))
110);
111
112impl Cheatcode for parseJsonType_0Call {
113 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
114 let Self { json, typeDescription } = self;
115 parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?)
116 .map(|v| v.abi_encode())
117 }
118}
119
120impl Cheatcode for parseJsonType_1Call {
121 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
122 let Self { json, key, typeDescription } = self;
123 parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?)
124 .map(|v| v.abi_encode())
125 }
126}
127
128impl Cheatcode for parseJsonTypeArrayCall {
129 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
130 let Self { json, key, typeDescription } = self;
131 let ty = resolve_type(typeDescription, state.struct_defs())?;
132 parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode())
133 }
134}
135
136impl Cheatcode for parseJsonKeysCall {
137 fn apply_context_free(&self) -> Result {
138 let Self { json, key } = self;
139 parse_json_keys(json, key)
140 }
141}
142
143impl Cheatcode for serializeJsonCall {
144 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
145 let Self { objectKey, value } = self;
146 *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?;
147 Ok(value.abi_encode())
148 }
149}
150
151impl Cheatcode for serializeBool_0Call {
152 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
153 let Self { objectKey, valueKey, value } = self;
154 serialize_json(state, objectKey, valueKey, (*value).into())
155 }
156}
157
158impl Cheatcode for serializeUint_0Call {
159 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
160 let Self { objectKey, valueKey, value } = self;
161 serialize_json(state, objectKey, valueKey, (*value).into())
162 }
163}
164
165impl Cheatcode for serializeInt_0Call {
166 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
167 let Self { objectKey, valueKey, value } = self;
168 serialize_json(state, objectKey, valueKey, (*value).into())
169 }
170}
171
172impl Cheatcode for serializeAddress_0Call {
173 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
174 let Self { objectKey, valueKey, value } = self;
175 serialize_json(state, objectKey, valueKey, (*value).into())
176 }
177}
178
179impl Cheatcode for serializeBytes32_0Call {
180 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
181 let Self { objectKey, valueKey, value } = self;
182 serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32))
183 }
184}
185
186impl Cheatcode for serializeString_0Call {
187 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
188 let Self { objectKey, valueKey, value } = self;
189 serialize_json(state, objectKey, valueKey, value.clone().into())
190 }
191}
192
193impl Cheatcode for serializeBytes_0Call {
194 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
195 let Self { objectKey, valueKey, value } = self;
196 serialize_json(state, objectKey, valueKey, value.to_vec().into())
197 }
198}
199
200impl Cheatcode for serializeBool_1Call {
201 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
202 let Self { objectKey, valueKey, values } = self;
203 serialize_json(
204 state,
205 objectKey,
206 valueKey,
207 DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()),
208 )
209 }
210}
211
212impl Cheatcode for serializeUint_1Call {
213 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
214 let Self { objectKey, valueKey, values } = self;
215 serialize_json(
216 state,
217 objectKey,
218 valueKey,
219 DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()),
220 )
221 }
222}
223
224impl Cheatcode for serializeInt_1Call {
225 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
226 let Self { objectKey, valueKey, values } = self;
227 serialize_json(
228 state,
229 objectKey,
230 valueKey,
231 DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()),
232 )
233 }
234}
235
236impl Cheatcode for serializeAddress_1Call {
237 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
238 let Self { objectKey, valueKey, values } = self;
239 serialize_json(
240 state,
241 objectKey,
242 valueKey,
243 DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()),
244 )
245 }
246}
247
248impl Cheatcode for serializeBytes32_1Call {
249 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
250 let Self { objectKey, valueKey, values } = self;
251 serialize_json(
252 state,
253 objectKey,
254 valueKey,
255 DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()),
256 )
257 }
258}
259
260impl Cheatcode for serializeString_1Call {
261 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
262 let Self { objectKey, valueKey, values } = self;
263 serialize_json(
264 state,
265 objectKey,
266 valueKey,
267 DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()),
268 )
269 }
270}
271
272impl Cheatcode for serializeBytes_1Call {
273 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
274 let Self { objectKey, valueKey, values } = self;
275 serialize_json(
276 state,
277 objectKey,
278 valueKey,
279 DynSolValue::Array(
280 values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(),
281 ),
282 )
283 }
284}
285
286impl Cheatcode for serializeJsonType_0Call {
287 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
288 let Self { typeDescription, value } = self;
289 let ty = resolve_type(typeDescription, state.struct_defs())?;
290 let value = ty.abi_decode(value)?;
291 let value =
292 foundry_common::fmt::serialize_value_as_json(value, state.struct_defs(), false)?;
293 Ok(value.to_string().abi_encode())
294 }
295}
296
297impl Cheatcode for serializeJsonType_1Call {
298 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
299 let Self { objectKey, valueKey, typeDescription, value } = self;
300 let ty = resolve_type(typeDescription, state.struct_defs())?;
301 let value = ty.abi_decode(value)?;
302 serialize_json(state, objectKey, valueKey, value)
303 }
304}
305
306impl Cheatcode for serializeUintToHexCall {
307 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
308 let Self { objectKey, valueKey, value } = self;
309 let hex = format!("0x{value:x}");
310 serialize_json(state, objectKey, valueKey, hex.into())
311 }
312}
313
314impl Cheatcode for writeJson_0Call {
315 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
316 let Self { json, path } = self;
317 let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned()));
318 let json_string = serde_json::to_string_pretty(&json)?;
319 super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
320 }
321}
322
323impl Cheatcode for writeJson_1Call {
324 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
325 let Self { json: value, path, valueKey } = self;
326
327 let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
330 let mut data = if data_path.exists() {
331 let data_string = fs::locked_read_to_string(&data_path)?;
332 serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string))
333 } else {
334 Value::Object(Default::default())
335 };
336 upsert_json_value(&mut data, value, valueKey)?;
337
338 let json_string = serde_json::to_string_pretty(&data)?;
340 super::fs::write_file(state, path.as_ref(), json_string.as_bytes())
341 }
342}
343
344pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result {
345 let json = parse_json_str(json)?;
346 let values = select(&json, key)?;
347 let exists = !values.is_empty();
348 Ok(exists.abi_encode())
349}
350
351pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result {
352 let value = parse_json_str(json)?;
353 let selected = select(&value, path)?;
354 let sol = json_to_sol(defs, &selected)?;
355 Ok(encode(sol))
356}
357
358pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result {
359 let json = parse_json_str(json)?;
360 let [value] = select(&json, path)?[..] else {
361 bail!("path {path:?} must return exactly one JSON value");
362 };
363
364 parse_json_as(value, ty).map(|v| v.abi_encode())
365}
366
367pub(super) fn parse_json_coerce_default<T: SolValue>(
368 json: &str,
369 path: &str,
370 ty: &DynSolType,
371 default: &T,
372) -> Result {
373 let json = parse_json_str(json)?;
374 match select(&json, path)?.as_slice() {
375 [] => Ok(default.abi_encode()),
376 [value] => parse_json_as(value, ty).map(|value| value.abi_encode()),
377 _ => bail!("path {path:?} must return exactly one JSON value"),
378 }
379}
380
381pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result<DynSolValue> {
383 let to_string = |v: &Value| {
384 let mut s = v.to_string();
385 s.retain(|c: char| c != '"');
386 s
387 };
388
389 match (value, ty) {
390 (Value::Array(array), ty) => parse_json_array(array, ty),
391 (Value::Object(object), ty) => parse_json_map(object, ty),
392 (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
393 (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty),
394 _ => string::parse_value(&to_string(value), ty),
395 }
396}
397
398pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result<DynSolValue> {
399 match ty {
400 DynSolType::Tuple(types) => {
401 ensure!(array.len() == types.len(), "array length mismatch");
402 let values = array
403 .iter()
404 .zip(types)
405 .map(|(e, ty)| parse_json_as(e, ty))
406 .collect::<Result<Vec<_>>>()?;
407
408 Ok(DynSolValue::Tuple(values))
409 }
410 DynSolType::Array(inner) => {
411 let values =
412 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
413 Ok(DynSolValue::Array(values))
414 }
415 DynSolType::FixedArray(inner, len) => {
416 ensure!(array.len() == *len, "array length mismatch");
417 let values =
418 array.iter().map(|e| parse_json_as(e, inner)).collect::<Result<Vec<_>>>()?;
419 Ok(DynSolValue::FixedArray(values))
420 }
421 _ => bail!("expected {ty}, found array"),
422 }
423}
424
425pub(super) fn parse_json_map(map: &Map<String, Value>, ty: &DynSolType) -> Result<DynSolValue> {
426 let Some((name, fields, types)) = ty.as_custom_struct() else {
427 bail!("expected {ty}, found JSON object");
428 };
429
430 let mut values = Vec::with_capacity(fields.len());
431 for (field, ty) in fields.iter().zip(types.iter()) {
432 let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") };
433 values.push(parse_json_as(value, ty)?);
434 }
435
436 Ok(DynSolValue::CustomStruct {
437 name: name.to_string(),
438 prop_names: fields.to_vec(),
439 tuple: values,
440 })
441}
442
443pub(super) fn parse_json_keys(json: &str, key: &str) -> Result {
444 let json = parse_json_str(json)?;
445 let values = select(&json, key)?;
446 let [value] = values[..] else {
447 bail!("key {key:?} must return exactly one JSON object");
448 };
449 let Value::Object(object) = value else {
450 bail!("JSON value at {key:?} is not an object");
451 };
452 let keys = object.keys().collect::<Vec<_>>();
453 Ok(keys.abi_encode())
454}
455
456pub(super) fn parse_json_array_length(json: &str, key: &str) -> Result {
457 let json = parse_json_str(json)?;
458 let values = select(&json, key)?;
459 let [value] = values[..] else {
460 bail!("key {key:?} must return exactly one JSON array");
461 };
462 let Value::Array(array) = value else {
463 bail!("JSON value at {key:?} is not an array");
464 };
465 Ok(U256::from(array.len()).abi_encode())
466}
467
468fn parse_json_str(json: &str) -> Result<Value> {
469 let json = strip_json_comments(json)?;
470 serde_json::from_str(&json).map_err(|e| fmt_err!("failed parsing JSON: {e}"))
471}
472
473fn strip_json_comments(json: &str) -> Result<Cow<'_, str>> {
474 let bytes = json.as_bytes();
475 let mut stripped = None;
476 let mut index = 0;
477 let mut in_string = false;
478
479 while index < bytes.len() {
480 if in_string {
481 match bytes[index] {
482 b'\\' => index += 2,
483 b'"' => {
484 in_string = false;
485 index += 1;
486 }
487 _ => index += 1,
488 }
489 continue;
490 }
491
492 match (bytes[index], bytes.get(index + 1)) {
493 (b'"', _) => {
494 in_string = true;
495 index += 1;
496 }
497 (b'/', Some(b'/')) => {
498 let stripped = stripped.get_or_insert_with(|| bytes.to_vec());
499 while index < bytes.len() && !matches!(bytes[index], b'\r' | b'\n') {
500 stripped[index] = b' ';
501 index += 1;
502 }
503 }
504 (b'/', Some(b'*')) => {
505 let stripped = stripped.get_or_insert_with(|| bytes.to_vec());
506 stripped[index] = b' ';
507 stripped[index + 1] = b' ';
508 index += 2;
509
510 let mut closed = false;
511 while index < bytes.len() {
512 if bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/') {
513 stripped[index] = b' ';
514 stripped[index + 1] = b' ';
515 index += 2;
516 closed = true;
517 break;
518 }
519 if !matches!(bytes[index], b'\r' | b'\n') {
520 stripped[index] = b' ';
521 }
522 index += 1;
523 }
524 ensure!(closed, "failed parsing JSON: unterminated block comment");
525 }
526 _ => index += 1,
527 }
528 }
529
530 Ok(match stripped {
531 Some(stripped) => {
532 String::from_utf8(stripped).expect("comment stripping preserves valid UTF-8").into()
533 }
534 None => json.into(),
535 })
536}
537
538fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result<Vec<DynSolValue>> {
539 let mut sol = Vec::with_capacity(json.len());
540 for value in json {
541 sol.push(json_value_to_token(value, defs)?);
542 }
543 Ok(sol)
544}
545
546fn select<'a>(value: &'a Value, mut path: &str) -> Result<Vec<&'a Value>> {
547 if path == "." {
549 path = "$";
550 }
551 jsonpath_lib::select(value, &canonicalize_json_path(path))
553 .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string()))
554}
555
556fn encode(values: Vec<DynSolValue>) -> Vec<u8> {
557 let bytes = match &values[..] {
559 [] => Vec::new(),
560 [one] => one.abi_encode(),
561 _ => DynSolValue::Array(values).abi_encode(),
562 };
563 bytes.abi_encode()
564}
565
566pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> {
569 if path.starts_with('$') { path.into() } else { format!("${path}").into() }
570}
571
572#[instrument(target = "cheatcodes", level = "trace", ret)]
579pub(super) fn json_value_to_token(
580 value: &Value,
581 defs: Option<&StructDefinitions>,
582) -> Result<DynSolValue> {
583 if let Some(defs) = defs {
584 _json_value_to_token(value, defs)
585 } else {
586 _json_value_to_token(value, &StructDefinitions::default())
587 }
588}
589
590fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result<DynSolValue> {
591 match value {
592 Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)),
593 Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)),
594 Value::Array(array) => array
595 .iter()
596 .map(|v| _json_value_to_token(v, defs))
597 .collect::<Result<_>>()
598 .map(DynSolValue::Array),
599 Value::Object(map) => {
600 let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect();
602 let matching_defs = defs
603 .values()
604 .filter(|fields| {
605 fields.len() == keys.len()
606 && fields.iter().map(|(name, _)| name.as_str()).collect::<BTreeSet<_>>()
607 == keys
608 })
609 .collect::<Vec<_>>();
610
611 if let Some(fields) = matching_defs.first() {
612 fields
614 .iter()
615 .map(|(name, type_description)| {
616 let value = map.get(name).unwrap();
618 let unambiguous = matching_defs.iter().all(|fields| {
619 fields
620 .iter()
621 .find(|(field, _)| field == name)
622 .is_some_and(|(_, ty)| ty == type_description)
623 });
624 if unambiguous
625 && let Some(parsed) = parse_fixed_array(value, type_description, defs)
626 {
627 parsed
628 } else {
629 _json_value_to_token(value, defs)
630 }
631 })
632 .collect::<Result<_>>()
633 .map(DynSolValue::Tuple)
634 } else {
635 let ordered_object: BTreeMap<_, _> =
638 map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
639 ordered_object
640 .values()
641 .map(|value| _json_value_to_token(value, defs))
642 .collect::<Result<_>>()
643 .map(DynSolValue::Tuple)
644 }
645 }
646 Value::Number(number) => {
647 if let Some(f) = number.as_f64() {
648 if f.fract() == 0.0 {
651 let s = number.to_string();
657
658 if s.contains('e') {
663 let fallback_s = f.to_string();
669 if let Ok(n) = fallback_s.parse() {
670 return Ok(DynSolValue::Uint(n, 256));
671 }
672 if let Ok(n) = I256::from_dec_str(&fallback_s) {
673 return Ok(DynSolValue::Int(n, 256));
674 }
675 }
676
677 if let Ok(n) = s.parse() {
678 return Ok(DynSolValue::Uint(n, 256));
679 }
680 if let Ok(n) = s.parse() {
681 return Ok(DynSolValue::Int(n, 256));
682 }
683 }
684 }
685
686 Err(fmt_err!("unsupported JSON number: {number}"))
687 }
688 Value::String(string) => {
689 if let Some(mut val) = string.strip_prefix("0x") {
691 let s;
692 if val.len() == 39 {
693 return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into());
694 }
695 if !val.len().is_multiple_of(2) {
696 s = format!("0{val}");
697 val = &s[..];
698 }
699 if let Ok(bytes) = hex::decode(val) {
700 return Ok(match bytes.len() {
701 20 => DynSolValue::Address(Address::from_slice(&bytes)),
702 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32),
703 _ => DynSolValue::Bytes(bytes),
704 });
705 }
706 }
707
708 if let Ok(n) = string.parse::<I256>()
713 && i64::try_from(n).is_err()
714 {
715 return Ok(DynSolValue::Int(n, 256));
716 } else if let Ok(n) = string.parse::<U256>()
717 && u64::try_from(n).is_err()
718 {
719 return Ok(DynSolValue::Uint(n, 256));
720 }
721
722 Ok(DynSolValue::String(string.to_owned()))
724 }
725 }
726}
727
728fn parse_fixed_array(
729 value: &Value,
730 type_description: &str,
731 defs: &StructDefinitions,
732) -> Option<Result<DynSolValue>> {
733 let root_end = type_description.find('[')?;
734 let root = &type_description[..root_end];
735 let (ty, custom) = match defs.get(root) {
736 Ok(Some(_)) => {
737 (DynSolType::parse(&format!("bool{}", &type_description[root_end..])).ok()?, true)
738 }
739 Ok(None) if root.contains('.') => return None,
740 Ok(None) => (DynSolType::parse(type_description).ok()?, false),
741 Err(_) => return None,
742 };
743 if !contains_fixed_array(&ty) {
744 return None;
745 }
746
747 Some(if custom { parse_json_custom_array(value, &ty, defs) } else { parse_json_as(value, &ty) })
748}
749
750fn parse_json_custom_array(
751 value: &Value,
752 ty: &DynSolType,
753 defs: &StructDefinitions,
754) -> Result<DynSolValue> {
755 match (value, ty) {
756 (Value::Array(values), DynSolType::Array(inner)) => values
757 .iter()
758 .map(|value| parse_json_custom_array(value, inner, defs))
759 .collect::<Result<_>>()
760 .map(DynSolValue::Array),
761 (Value::Array(values), DynSolType::FixedArray(inner, len)) => {
762 ensure!(values.len() == *len, "array length mismatch");
763 values
764 .iter()
765 .map(|value| parse_json_custom_array(value, inner, defs))
766 .collect::<Result<_>>()
767 .map(DynSolValue::FixedArray)
768 }
769 (_, DynSolType::Bool) => _json_value_to_token(value, defs),
770 _ => bail!("expected array"),
771 }
772}
773
774fn contains_fixed_array(ty: &DynSolType) -> bool {
775 match ty {
776 DynSolType::FixedArray(_, _) => true,
777 DynSolType::Array(inner) => contains_fixed_array(inner),
778 _ => false,
779 }
780}
781
782fn serialize_json(
791 state: &mut CheatcodesCore,
792 object_key: &str,
793 value_key: &str,
794 value: DynSolValue,
795) -> Result {
796 let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs(), false)?;
797 let map = state.serialized_jsons.entry(object_key.into()).or_default();
798 map.insert(value_key.into(), value);
799 let stringified = serde_json::to_string(map).unwrap();
800 Ok(stringified.abi_encode())
801}
802
803pub(super) fn resolve_type(
805 type_description: &str,
806 struct_defs: Option<&StructDefinitions>,
807) -> Result<DynSolType> {
808 let ordered_ty = |ty| -> Result<DynSolType> {
809 if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) }
810 };
811
812 if let Ok(ty) = DynSolType::parse(type_description) {
813 return ordered_ty(ty);
814 };
815
816 if let Ok(encoded) = EncodeType::parse(type_description)
817 && let Some(main) = encoded.types.first()
818 {
819 let main_type = main.type_name;
820 let mut resolver = Resolver::default();
821 for t in &encoded.types {
822 resolver.ingest(t.to_owned());
823 }
824
825 return ordered_ty(resolver.resolve(main_type)?);
827 }
828
829 bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string")
830}
831
832pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> {
845 let parts = split_value_key(key)?;
846
847 if let Some((key_to_insert, path_to_parent)) = parts.split_last() {
850 let mut current_level = data;
851
852 for segment in path_to_parent {
853 if !current_level.is_object() {
854 return Err(fmt_err!("path segment '{segment}' does not resolve to an object."));
855 }
856 current_level = current_level
857 .as_object_mut()
858 .unwrap()
859 .entry(segment.clone())
860 .or_insert(Value::Object(Map::new()));
861 }
862
863 if let Some(parent_obj) = current_level.as_object_mut() {
865 parent_obj.insert(
866 key_to_insert.clone(),
867 serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())),
868 );
869 } else {
870 return Err(fmt_err!("final destination is not an object, cannot insert key."));
871 }
872 }
873
874 Ok(())
875}
876
877pub(super) fn split_value_key(key: &str) -> Result<Vec<String>> {
879 let canonical_key = canonicalize_json_path(key);
881 let parts = canonical_key
882 .strip_prefix("$.")
883 .unwrap_or(key)
884 .split('.')
885 .filter(|s| !s.is_empty())
886 .map(str::to_string)
887 .collect::<Vec<_>>();
888
889 if parts.is_empty() {
890 return Err(fmt_err!("'valueKey' cannot be empty or just '$'"));
891 }
892
893 Ok(parts)
894}
895
896fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result<DynSolType> {
902 match ty {
903 DynSolType::CustomStruct { name, prop_names, tuple } => {
904 if let Some(def) = struct_defs.get(&name)? {
905 let type_map: std::collections::HashMap<String, DynSolType> =
907 prop_names.into_iter().zip(tuple).collect();
908
909 let mut sorted_props = Vec::with_capacity(def.len());
910 let mut sorted_tuple = Vec::with_capacity(def.len());
911 for (field_name, _) in def {
912 sorted_props.push(field_name.clone());
913 if let Some(field_ty) = type_map.get(field_name) {
914 sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?);
915 } else {
916 bail!(
917 "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'"
918 );
919 }
920 }
921 Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple })
922 } else {
923 let new_tuple = tuple
926 .into_iter()
927 .map(|t| reorder_type(t, struct_defs))
928 .collect::<Result<Vec<_>>>()?;
929 Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple })
930 }
931 }
932 DynSolType::Array(inner) => {
933 Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?)))
934 }
935 DynSolType::FixedArray(inner, len) => {
936 Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len))
937 }
938 DynSolType::Tuple(inner) => Ok(DynSolType::Tuple(
939 inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::<Result<Vec<_>>>()?,
940 )),
941 _ => Ok(ty),
942 }
943}
944
945#[cfg(test)]
946mod tests {
947 use super::*;
948 use alloy_primitives::FixedBytes;
949 use foundry_common::fmt::{TypeDefMap, serialize_value_as_json};
950 use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
951 use std::collections::HashSet;
952
953 #[test]
954 fn test_parse_json_comments() {
955 let value = parse_json_str(
956 r#"{
957 // A line comment.
958 "value": 42,
959 /* A block comment. */
960 "url": "https://example.com/path/*literal*/"
961 }"#,
962 )
963 .unwrap();
964
965 assert_eq!(value["value"], 42);
966 assert_eq!(value["url"], "https://example.com/path/*literal*/");
967
968 let error = parse_json_str(r#"{"value": 42} /* unterminated"#).unwrap_err();
969 assert!(error.to_string().contains("unterminated block comment"));
970 }
971
972 fn valid_value(value: &DynSolValue) -> bool {
973 (match value {
974 DynSolValue::String(s) if s == "{}" => false,
975
976 DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false,
977
978 DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value),
979 _ => true,
980 }) && value.as_type().is_some()
981 }
982
983 fn fixup_guessable(value: DynSolValue) -> DynSolValue {
988 match value {
989 DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => {
990 if let Some(DynSolValue::Bytes(_)) = v.first() {
991 v.retain(|v| {
992 let len = v.as_bytes().unwrap().len();
993 len != 32 && len != 20
994 })
995 }
996 DynSolValue::Array(v.into_iter().map(fixup_guessable).collect())
997 }
998 DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32),
999 DynSolValue::Bytes(v) if v.len() == 32 => {
1000 DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32)
1001 }
1002 DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)),
1003 _ => value,
1004 }
1005 }
1006
1007 fn guessable_types() -> impl proptest::strategy::Strategy<Value = DynSolValue> {
1008 any::<DynSolValue>().prop_map(fixup_guessable).prop_filter("invalid value", valid_value)
1009 }
1010
1011 fn custom_struct_strategy() -> impl Strategy<Value = (StructDefinitions, DynSolValue)> {
1014 let field_name_strat = "[a-z]{4,12}";
1016 let field_value_strat = prop_oneof![
1017 any::<bool>().prop_map(DynSolValue::Bool),
1018 any::<u32>().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)),
1019 any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address),
1020 any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)),
1021 ".*".prop_filter("invalid string value", |s| s != "{}").prop_map(DynSolValue::String),
1022 ];
1023
1024 let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8)
1026 .prop_map(|fields| {
1027 let mut unique_fields = Vec::with_capacity(fields.len());
1028 let mut seen_names = HashSet::new();
1029 for (name, value) in fields {
1030 if seen_names.insert(name.clone()) {
1031 unique_fields.push((name, value));
1032 }
1033 }
1034 unique_fields
1035 });
1036
1037 ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| {
1039 let (prop_names, tuple): (Vec<String>, Vec<DynSolValue>) =
1040 fields.clone().into_iter().unzip();
1041 let def_fields: Vec<(String, String)> = fields
1042 .iter()
1043 .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string()))
1044 .collect();
1045 let mut defs_map = TypeDefMap::default();
1046 defs_map.insert(struct_name.clone(), def_fields);
1047 (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple })
1048 })
1049 }
1050
1051 proptest::proptest! {
1053 #[test]
1054 fn test_json_roundtrip_guessed(v in guessable_types()) {
1055 let json = serialize_value_as_json(v.clone(), None, false).unwrap();
1056 let value = json_value_to_token(&json, None).unwrap();
1057
1058 let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap();
1060 assert_eq!(decoded, v);
1061 }
1062
1063 #[test]
1064 fn test_json_roundtrip(v in any::<DynSolValue>().prop_filter("filter out values without type", |v| v.as_type().is_some())) {
1065 let json = serialize_value_as_json(v.clone(), None, false).unwrap();
1066 let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap();
1067 assert_eq!(value, v);
1068 }
1069
1070 #[test]
1071 fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) {
1072 let json = serialize_value_as_json(v.clone(), Some(&struct_defs), false).unwrap();
1073 let sol_type = v.as_type().unwrap();
1074 let parsed_value = parse_json_as(&json, &sol_type).unwrap();
1075 assert_eq!(parsed_value, v);
1076 }
1077 }
1078
1079 #[test]
1080 fn test_resolve_type_with_definitions() -> Result<()> {
1081 let mut struct_defs = TypeDefMap::new();
1083 struct_defs.insert(
1084 "Apple".to_string(),
1085 vec![
1086 ("color".to_string(), "string".to_string()),
1087 ("sweetness".to_string(), "uint8".to_string()),
1088 ("sourness".to_string(), "uint8".to_string()),
1089 ],
1090 );
1091 struct_defs.insert(
1092 "FruitStall".to_string(),
1093 vec![
1094 ("name".to_string(), "string".to_string()),
1095 ("apples".to_string(), "Apple[]".to_string()),
1096 ],
1097 );
1098
1099 let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)";
1101
1102 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
1104 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
1105 assert_eq!(name, "FruitStall");
1106 assert_eq!(prop_names, vec!["name", "apples"]);
1107 assert_eq!(tuple.len(), 2);
1108 assert_eq!(tuple[0], DynSolType::String);
1109
1110 if let DynSolType::Array(apple_ty_boxed) = &tuple[1]
1111 && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed
1112 {
1113 assert_eq!(*name, "Apple");
1114 assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]);
1116 assert_eq!(
1117 *tuple,
1118 vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)]
1119 );
1120
1121 return Ok(());
1122 }
1123 }
1124 panic!("Expected FruitStall and Apple to be CustomStruct");
1125 }
1126
1127 #[test]
1128 fn test_resolve_type_without_definitions() -> Result<()> {
1129 let ty_desc = "Person(bool active,uint256 age,string name)";
1131
1132 let ty = resolve_type(ty_desc, None).unwrap();
1135 if let DynSolType::CustomStruct { name, prop_names, tuple } = ty {
1136 assert_eq!(name, "Person");
1137 assert_eq!(prop_names, vec!["active", "age", "name"]);
1138 assert_eq!(tuple.len(), 3);
1139 assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]);
1140 return Ok(());
1141 }
1142 panic!("Expected Person to be CustomStruct");
1143 }
1144
1145 #[test]
1146 fn test_resolve_type_bare_struct_name_errors_instead_of_panicking() {
1147 let err = resolve_type("Foo", None).unwrap_err();
1149 assert!(
1150 err.to_string().contains("valid Solidity type or a EIP712 `encodeType` string"),
1151 "unexpected error: {err}"
1152 );
1153 }
1154
1155 #[test]
1156 fn test_parse_fixed_array() {
1157 let mut struct_defs = TypeDefMap::new();
1158 struct_defs.insert(
1159 "Contract.Child".to_string(),
1160 vec![("value".to_string(), "uint256".to_string())],
1161 );
1162 let struct_defs = StructDefinitions::from(struct_defs);
1163
1164 let value = serde_json::json!([[1], [2]]);
1165 assert!(parse_fixed_array(&value, "uint256[1][]", &struct_defs).unwrap().is_ok());
1166 assert!(parse_fixed_array(&value, "uint256[", &struct_defs).is_none());
1167 assert!(parse_fixed_array(&value, "uint256[0]", &struct_defs).is_none());
1168 assert!(parse_fixed_array(&value, "Contract.Child[", &struct_defs).is_none());
1169 assert!(parse_fixed_array(&value, "Missing.Child[1]", &struct_defs).is_none());
1170
1171 let value = serde_json::json!([[{"value": 1}, {"value": 2}]]);
1172 let parsed =
1173 parse_fixed_array(&value, "Contract.Child[2][1]", &struct_defs).unwrap().unwrap();
1174 assert!(matches!(
1175 parsed,
1176 DynSolValue::FixedArray(outer)
1177 if matches!(&outer[..], [DynSolValue::FixedArray(inner)] if matches!(&inner[..], [DynSolValue::Tuple(_), DynSolValue::Tuple(_)]))
1178 ));
1179
1180 let value = serde_json::json!([[{"value": 1}], [{"value": 2}]]);
1181 assert!(parse_fixed_array(&value, "Contract.Child[1][]", &struct_defs).unwrap().is_ok());
1182 let value = serde_json::json!([[]]);
1183 assert!(parse_fixed_array(&value, "Contract.Child[][1]", &struct_defs).unwrap().is_ok());
1184
1185 let mut ambiguous_defs = TypeDefMap::new();
1186 ambiguous_defs
1187 .insert("A.Child".to_string(), vec![("value".to_string(), "uint256".to_string())]);
1188 ambiguous_defs
1189 .insert("B.Child".to_string(), vec![("value".to_string(), "uint256".to_string())]);
1190 let ambiguous_defs = StructDefinitions::from(ambiguous_defs);
1191 let value = serde_json::json!([{"value": 1}]);
1192 assert!(parse_fixed_array(&value, "Child[1]", &ambiguous_defs).is_none());
1193 assert!(parse_fixed_array(&value, "A.Child[1]", &ambiguous_defs).unwrap().is_ok());
1194 }
1195
1196 #[test]
1197 fn test_resolve_type_for_array_of_structs() -> Result<()> {
1198 let mut struct_defs = TypeDefMap::new();
1200 struct_defs.insert(
1201 "Item".to_string(),
1202 vec![
1203 ("name".to_string(), "string".to_string()),
1204 ("price".to_string(), "uint256".to_string()),
1205 ("id".to_string(), "uint256".to_string()),
1206 ],
1207 );
1208
1209 let ty_desc = "Item(uint256 id,string name,uint256 price)";
1211
1212 let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap();
1214 let array_ty = DynSolType::Array(Box::new(ty));
1215 if let DynSolType::Array(item_ty) = array_ty
1216 && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty
1217 {
1218 assert_eq!(name, "Item");
1219 assert_eq!(prop_names, vec!["name", "price", "id"]);
1220 assert_eq!(
1221 tuple,
1222 vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)]
1223 );
1224 return Ok(());
1225 }
1226 panic!("Expected CustomStruct in array");
1227 }
1228
1229 #[test]
1230 fn test_parse_json_missing_field() {
1231 let mut struct_defs = TypeDefMap::new();
1233 struct_defs.insert(
1234 "Person".to_string(),
1235 vec![
1236 ("name".to_string(), "string".to_string()),
1237 ("age".to_string(), "uint256".to_string()),
1238 ],
1239 );
1240
1241 let json_str = r#"{ "name": "Alice" }"#;
1243
1244 let type_description = "Person(uint256 age,string name)";
1246 let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1247
1248 let json_value: Value = serde_json::from_str(json_str).unwrap();
1250 let result = parse_json_as(&json_value, &ty);
1251
1252 assert!(result.is_err());
1254 assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object"));
1255 }
1256
1257 #[test]
1258 fn test_serialize_json_with_struct_def_order() {
1259 let mut struct_defs = TypeDefMap::new();
1261 struct_defs.insert(
1262 "Item".to_string(),
1263 vec![
1264 ("name".to_string(), "string".to_string()),
1265 ("id".to_string(), "uint256".to_string()),
1266 ("active".to_string(), "bool".to_string()),
1267 ],
1268 );
1269
1270 let item_struct = DynSolValue::CustomStruct {
1272 name: "Item".to_string(),
1273 prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()],
1274 tuple: vec![
1275 DynSolValue::String("Test Item".to_string()),
1276 DynSolValue::Uint(U256::from(123), 256),
1277 DynSolValue::Bool(true),
1278 ],
1279 };
1280
1281 let json_value =
1283 serialize_value_as_json(item_struct, Some(&struct_defs.into()), false).unwrap();
1284 let json_string = serde_json::to_string(&json_value).unwrap();
1285 assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#);
1286 }
1287
1288 #[test]
1289 fn test_json_full_cycle_typed_with_struct_defs() {
1290 let mut struct_defs = TypeDefMap::new();
1292 struct_defs.insert(
1293 "Wallet".to_string(),
1294 vec![
1295 ("owner".to_string(), "address".to_string()),
1296 ("balance".to_string(), "uint256".to_string()),
1297 ("id".to_string(), "bytes32".to_string()),
1298 ],
1299 );
1300
1301 let owner_address = Address::repeat_byte(1);
1303 let wallet_id = B256::repeat_byte(2);
1304 let original_wallet = DynSolValue::CustomStruct {
1305 name: "Wallet".to_string(),
1306 prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()],
1307 tuple: vec![
1308 DynSolValue::Address(owner_address),
1309 DynSolValue::Uint(U256::from(5000), 256),
1310 DynSolValue::FixedBytes(wallet_id, 32),
1311 ],
1312 };
1313
1314 let json_value = serialize_value_as_json(
1316 original_wallet.clone(),
1317 Some(&struct_defs.clone().into()),
1318 false,
1319 )
1320 .unwrap();
1321 let json_string = serde_json::to_string(&json_value).unwrap();
1322 assert_eq!(
1323 json_string,
1324 format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#)
1325 );
1326
1327 let type_description = "Wallet(uint256 balance,bytes32 id,address owner)";
1329 let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap();
1330
1331 let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap();
1334 assert_eq!(parsed_value, original_wallet);
1335 }
1336}