Skip to content

Commit a966fba

Browse files
authored
fix(cast): calldata-decode --json nested tuple formatting (#11212)
* fix(cast): calldata-decode --json nested tuple formatting * fix: clippy * fix: remove redundant branches * fix: idomatic * chore: calldata_decode_array test * fix: rename test * fix: avoid pulling in scope * feat: move serialize_value_as_json to common * docs: update doc comment
1 parent 8481cfb commit a966fba

File tree

8 files changed

+105
-52
lines changed

8 files changed

+105
-52
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cast/src/args.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use eyre::Result;
1616
use foundry_cli::{handler, utils, utils::LoadConfig};
1717
use foundry_common::{
1818
abi::{get_error, get_event},
19-
fmt::{format_tokens, format_tokens_raw, format_uint_exp},
19+
fmt::{format_tokens, format_uint_exp, serialize_value_as_json},
2020
fs,
2121
selectors::{
2222
ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
@@ -751,13 +751,18 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
751751
}
752752
};
753753

754-
/// Prints slice of tokens using [`format_tokens`] or [`format_tokens_raw`] depending whether
755-
/// the shell is in JSON mode.
754+
/// Prints slice of tokens using [`format_tokens`] or [`serialize_value_as_json`] depending
755+
/// whether the shell is in JSON mode.
756756
///
757757
/// This is included here to avoid a cyclic dependency between `fmt` and `common`.
758758
fn print_tokens(tokens: &[DynSolValue]) {
759759
if shell::is_json() {
760-
let tokens: Vec<String> = format_tokens_raw(tokens).collect();
760+
let tokens: Vec<serde_json::Value> = tokens
761+
.iter()
762+
.cloned()
763+
.map(serialize_value_as_json)
764+
.collect::<Result<Vec<_>>>()
765+
.unwrap();
761766
let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap());
762767
} else {
763768
let tokens = format_tokens(tokens);

crates/cast/src/lib.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2307,7 +2307,7 @@ fn explorer_client(
23072307

23082308
#[cfg(test)]
23092309
mod tests {
2310-
use super::SimpleCast as Cast;
2310+
use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json};
23112311
use alloy_primitives::hex;
23122312

23132313
#[test]
@@ -2425,6 +2425,47 @@ mod tests {
24252425
);
24262426
}
24272427

2428+
#[test]
2429+
fn calldata_decode_nested_json() {
2430+
let calldata = "0xdb5b0ed700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772bf190000000000000000000000000000000000000000000000000000000000020716000000000000000000000000af9d27ffe4d51ed54ac8eec78f2785d7e11e5ab100000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000404366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb73188600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5";
2431+
let sig = "sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],uint64,uint64,address,bytes)";
2432+
let decoded = Cast::calldata_decode(sig, calldata, true).unwrap();
2433+
let json_value = serialize_value_as_json(DynSolValue::Array(decoded)).unwrap();
2434+
let expected = serde_json::json!([
2435+
[
2436+
[
2437+
"0x04366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27",
2438+
"0x0000000000000000000000000000000000000000000000000000000000000000",
2439+
0,
2440+
"0x0000000000000000000000000000000000000000000000000000000000000000"
2441+
],
2442+
[
2443+
"0x093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed74",
2444+
"0x0000000000000000000000000000000000000000000000000000000000000000",
2445+
0,
2446+
"0x0000000000000000000000000000000000000000000000000000000000000000"
2447+
],
2448+
[
2449+
"0x60f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf7",
2450+
"0x0000000000000000000000000000000000000000000000000000000000000000",
2451+
0,
2452+
"0x0000000000000000000000000000000000000000000000000000000000000000"
2453+
],
2454+
[
2455+
"0x93dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb731886",
2456+
"0x0000000000000000000000000000000000000000000000000000000000000000",
2457+
0,
2458+
"0x0000000000000000000000000000000000000000000000000000000000000000"
2459+
]
2460+
],
2461+
1735573273,
2462+
132886,
2463+
"0xAF9d27ffe4d51eD54AC8eEc78f2785D7E11E5ab1",
2464+
"0x334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5"
2465+
]);
2466+
assert_eq!(json_value, expected);
2467+
}
2468+
24282469
#[test]
24292470
fn concat_hex() {
24302471
assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001");

crates/cast/tests/cli/selectors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ casttest!(event_decode_with_sig, |_prj, cmd| {
140140

141141
cmd.args(["--json"]).assert_success().stdout_eq(str![[r#"
142142
[
143-
"78",
143+
78,
144144
"0x0000000000000000000000000000000000D0004F"
145145
]
146146
@@ -168,7 +168,7 @@ casttest!(error_decode_with_sig, |_prj, cmd| {
168168

169169
cmd.args(["--json"]).assert_success().stdout_eq(str![[r#"
170170
[
171-
"101",
171+
101,
172172
"0x0000000000000000000000000000000000D0004F"
173173
]
174174

crates/cheatcodes/src/json.rs

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*, string};
44
use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, eip712_parser::EncodeType};
55
use alloy_primitives::{Address, B256, I256, hex};
66
use alloy_sol_types::SolValue;
7-
use foundry_common::fs;
7+
use foundry_common::{fmt::serialize_value_as_json, fs};
88
use foundry_config::fs_permissions::FsAccessKind;
99
use serde_json::{Map, Value};
1010
use std::{borrow::Cow, collections::BTreeMap};
@@ -585,49 +585,6 @@ pub(super) fn json_value_to_token(value: &Value) -> Result<DynSolValue> {
585585
}
586586
}
587587

588-
/// Serializes given [DynSolValue] into a [serde_json::Value].
589-
fn serialize_value_as_json(value: DynSolValue) -> Result<Value> {
590-
match value {
591-
DynSolValue::Bool(b) => Ok(Value::Bool(b)),
592-
DynSolValue::String(s) => {
593-
// Strings are allowed to contain stringified JSON objects, so we try to parse it like
594-
// one first.
595-
if let Ok(map) = serde_json::from_str(&s) {
596-
Ok(Value::Object(map))
597-
} else {
598-
Ok(Value::String(s))
599-
}
600-
}
601-
DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))),
602-
DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))),
603-
DynSolValue::Int(i, _) => {
604-
// let serde handle number parsing
605-
let n = serde_json::from_str(&i.to_string())?;
606-
Ok(Value::Number(n))
607-
}
608-
DynSolValue::Uint(i, _) => {
609-
// let serde handle number parsing
610-
let n = serde_json::from_str(&i.to_string())?;
611-
Ok(Value::Number(n))
612-
}
613-
DynSolValue::Address(a) => Ok(Value::String(a.to_string())),
614-
DynSolValue::Array(e) | DynSolValue::FixedArray(e) => {
615-
Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?))
616-
}
617-
DynSolValue::CustomStruct { name: _, prop_names, tuple } => {
618-
let values =
619-
tuple.into_iter().map(serialize_value_as_json).collect::<Result<Vec<_>>>()?;
620-
let map = prop_names.into_iter().zip(values).collect();
621-
622-
Ok(Value::Object(map))
623-
}
624-
DynSolValue::Tuple(values) => Ok(Value::Array(
625-
values.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?,
626-
)),
627-
DynSolValue::Function(_) => bail!("cannot serialize function pointer"),
628-
}
629-
}
630-
631588
/// Serializes a key:value pair to a specific object. If the key is valueKey, the value is
632589
/// expected to be an object, which will be set as the root object for the provided object key,
633590
/// overriding the whole root object if the object key already exists. By calling this function

crates/common/fmt/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ workspace = true
1616
[dependencies]
1717
alloy-primitives.workspace = true
1818
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
19+
eyre.workspace = true
1920

2021
# ui
2122
alloy-consensus.workspace = true

crates/common/fmt/src/dynamic.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::{format_int_exp, format_uint_exp};
22
use alloy_dyn_abi::{DynSolType, DynSolValue};
33
use alloy_primitives::hex;
4+
use eyre::Result;
5+
use serde_json::Value;
46
use std::fmt;
57

68
/// [`DynSolValue`] formatter.
@@ -146,6 +148,49 @@ pub fn format_token_raw(value: &DynSolValue) -> String {
146148
DynValueDisplay::new(value, true).to_string()
147149
}
148150

151+
/// Serializes given [DynSolValue] into a [serde_json::Value].
152+
pub fn serialize_value_as_json(value: DynSolValue) -> Result<Value> {
153+
match value {
154+
DynSolValue::Bool(b) => Ok(Value::Bool(b)),
155+
DynSolValue::String(s) => {
156+
// Strings are allowed to contain stringified JSON objects, so we try to parse it like
157+
// one first.
158+
if let Ok(map) = serde_json::from_str(&s) {
159+
Ok(Value::Object(map))
160+
} else {
161+
Ok(Value::String(s))
162+
}
163+
}
164+
DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))),
165+
DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))),
166+
DynSolValue::Int(i, _) => {
167+
// let serde handle number parsing
168+
let n = serde_json::from_str(&i.to_string())?;
169+
Ok(Value::Number(n))
170+
}
171+
DynSolValue::Uint(i, _) => {
172+
// let serde handle number parsing
173+
let n = serde_json::from_str(&i.to_string())?;
174+
Ok(Value::Number(n))
175+
}
176+
DynSolValue::Address(a) => Ok(Value::String(a.to_string())),
177+
DynSolValue::Array(e) | DynSolValue::FixedArray(e) => {
178+
Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::<Result<_, _>>()?))
179+
}
180+
DynSolValue::CustomStruct { name: _, prop_names, tuple } => {
181+
let values =
182+
tuple.into_iter().map(serialize_value_as_json).collect::<Result<Vec<_>>>()?;
183+
let map = prop_names.into_iter().zip(values).collect();
184+
185+
Ok(Value::Object(map))
186+
}
187+
DynSolValue::Tuple(values) => Ok(Value::Array(
188+
values.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?,
189+
)),
190+
DynSolValue::Function(_) => eyre::bail!("cannot serialize function pointer"),
191+
}
192+
}
193+
149194
#[cfg(test)]
150195
mod tests {
151196
use super::*;

crates/common/fmt/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ mod console;
66
pub use console::{ConsoleFmt, FormatSpec, console_format};
77

88
mod dynamic;
9-
pub use dynamic::{format_token, format_token_raw, format_tokens, format_tokens_raw, parse_tokens};
9+
pub use dynamic::{
10+
format_token, format_token_raw, format_tokens, format_tokens_raw, parse_tokens,
11+
serialize_value_as_json,
12+
};
1013

1114
mod exp;
1215
pub use exp::{format_int_exp, format_uint_exp, to_exp_notation};

0 commit comments

Comments
 (0)