Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions crates/cast/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use eyre::Result;
use foundry_cli::{handler, utils, utils::LoadConfig};
use foundry_common::{
abi::{get_error, get_event},
fmt::{format_tokens, format_tokens_raw, format_uint_exp},
fmt::{format_tokens, format_uint_exp, serialize_value_as_json},
fs,
selectors::{
ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
Expand Down Expand Up @@ -751,13 +751,18 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
}
};

/// Prints slice of tokens using [`format_tokens`] or [`format_tokens_raw`] depending whether
/// the shell is in JSON mode.
/// Prints slice of tokens using [`format_tokens`] or [`serialize_value_as_json`] depending
/// whether the shell is in JSON mode.
///
/// This is included here to avoid a cyclic dependency between `fmt` and `common`.
fn print_tokens(tokens: &[DynSolValue]) {
if shell::is_json() {
let tokens: Vec<String> = format_tokens_raw(tokens).collect();
let tokens: Vec<serde_json::Value> = tokens
.iter()
.cloned()
.map(serialize_value_as_json)
.collect::<Result<Vec<_>>>()
.unwrap();
let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap());
} else {
let tokens = format_tokens(tokens);
Expand Down
43 changes: 42 additions & 1 deletion crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2307,7 +2307,7 @@ fn explorer_client(

#[cfg(test)]
mod tests {
use super::SimpleCast as Cast;
use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json};
use alloy_primitives::hex;

#[test]
Expand Down Expand Up @@ -2425,6 +2425,47 @@ mod tests {
);
}

#[test]
fn calldata_decode_nested_json() {
let calldata = "0xdb5b0ed700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772bf190000000000000000000000000000000000000000000000000000000000020716000000000000000000000000af9d27ffe4d51ed54ac8eec78f2785d7e11e5ab100000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000404366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb73188600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5";
let sig = "sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],uint64,uint64,address,bytes)";
let decoded = Cast::calldata_decode(sig, calldata, true).unwrap();
let json_value = serialize_value_as_json(DynSolValue::Array(decoded)).unwrap();
let expected = serde_json::json!([
[
[
"0x04366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27",
"0x0000000000000000000000000000000000000000000000000000000000000000",
0,
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
[
"0x093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed74",
"0x0000000000000000000000000000000000000000000000000000000000000000",
0,
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
[
"0x60f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf7",
"0x0000000000000000000000000000000000000000000000000000000000000000",
0,
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
[
"0x93dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb731886",
"0x0000000000000000000000000000000000000000000000000000000000000000",
0,
"0x0000000000000000000000000000000000000000000000000000000000000000"
]
],
1735573273,
132886,
"0xAF9d27ffe4d51eD54AC8eEc78f2785D7E11E5ab1",
"0x334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5"
]);
assert_eq!(json_value, expected);
}

#[test]
fn concat_hex() {
assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001");
Expand Down
4 changes: 2 additions & 2 deletions crates/cast/tests/cli/selectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ casttest!(event_decode_with_sig, |_prj, cmd| {

cmd.args(["--json"]).assert_success().stdout_eq(str![[r#"
[
"78",
78,
"0x0000000000000000000000000000000000D0004F"
]

Expand Down Expand Up @@ -168,7 +168,7 @@ casttest!(error_decode_with_sig, |_prj, cmd| {

cmd.args(["--json"]).assert_success().stdout_eq(str![[r#"
[
"101",
101,
"0x0000000000000000000000000000000000D0004F"
]

Expand Down
45 changes: 1 addition & 44 deletions crates/cheatcodes/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*, string};
use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, eip712_parser::EncodeType};
use alloy_primitives::{Address, B256, I256, hex};
use alloy_sol_types::SolValue;
use foundry_common::fs;
use foundry_common::{fmt::serialize_value_as_json, fs};
use foundry_config::fs_permissions::FsAccessKind;
use serde_json::{Map, Value};
use std::{borrow::Cow, collections::BTreeMap};
Expand Down Expand Up @@ -585,49 +585,6 @@ pub(super) fn json_value_to_token(value: &Value) -> Result<DynSolValue> {
}
}

/// Serializes given [DynSolValue] into a [serde_json::Value].
fn serialize_value_as_json(value: DynSolValue) -> Result<Value> {
match value {
DynSolValue::Bool(b) => Ok(Value::Bool(b)),
DynSolValue::String(s) => {
// Strings are allowed to contain stringified JSON objects, so we try to parse it like
// one first.
if let Ok(map) = serde_json::from_str(&s) {
Ok(Value::Object(map))
} else {
Ok(Value::String(s))
}
}
DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))),
DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))),
DynSolValue::Int(i, _) => {
// let serde handle number parsing
let n = serde_json::from_str(&i.to_string())?;
Ok(Value::Number(n))
}
DynSolValue::Uint(i, _) => {
// let serde handle number parsing
let n = serde_json::from_str(&i.to_string())?;
Ok(Value::Number(n))
}
DynSolValue::Address(a) => Ok(Value::String(a.to_string())),
DynSolValue::Array(e) | DynSolValue::FixedArray(e) => {
Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?))
}
DynSolValue::CustomStruct { name: _, prop_names, tuple } => {
let values =
tuple.into_iter().map(serialize_value_as_json).collect::<Result<Vec<_>>>()?;
let map = prop_names.into_iter().zip(values).collect();

Ok(Value::Object(map))
}
DynSolValue::Tuple(values) => Ok(Value::Array(
values.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?,
)),
DynSolValue::Function(_) => bail!("cannot serialize function pointer"),
}
}

/// Serializes a key:value pair to a specific object. If the key is valueKey, the value is
/// expected to be an object, which will be set as the root object for the provided object key,
/// overriding the whole root object if the object key already exists. By calling this function
Expand Down
1 change: 1 addition & 0 deletions crates/common/fmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ workspace = true
[dependencies]
alloy-primitives.workspace = true
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
eyre.workspace = true

# ui
alloy-consensus.workspace = true
Expand Down
45 changes: 45 additions & 0 deletions crates/common/fmt/src/dynamic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::{format_int_exp, format_uint_exp};
use alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::hex;
use eyre::Result;
use serde_json::Value;
use std::fmt;

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

/// Serializes given [DynSolValue] into a [serde_json::Value].
pub fn serialize_value_as_json(value: DynSolValue) -> Result<Value> {
match value {
DynSolValue::Bool(b) => Ok(Value::Bool(b)),
DynSolValue::String(s) => {
// Strings are allowed to contain stringified JSON objects, so we try to parse it like
// one first.
if let Ok(map) = serde_json::from_str(&s) {
Ok(Value::Object(map))
} else {
Ok(Value::String(s))
}
}
DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))),
DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))),
DynSolValue::Int(i, _) => {
// let serde handle number parsing
let n = serde_json::from_str(&i.to_string())?;
Ok(Value::Number(n))
}
DynSolValue::Uint(i, _) => {
// let serde handle number parsing
let n = serde_json::from_str(&i.to_string())?;
Ok(Value::Number(n))
}
DynSolValue::Address(a) => Ok(Value::String(a.to_string())),
DynSolValue::Array(e) | DynSolValue::FixedArray(e) => {
Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::<Result<_, _>>()?))
}
DynSolValue::CustomStruct { name: _, prop_names, tuple } => {
let values =
tuple.into_iter().map(serialize_value_as_json).collect::<Result<Vec<_>>>()?;
let map = prop_names.into_iter().zip(values).collect();

Ok(Value::Object(map))
}
DynSolValue::Tuple(values) => Ok(Value::Array(
values.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?,
)),
DynSolValue::Function(_) => eyre::bail!("cannot serialize function pointer"),
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 4 additions & 1 deletion crates/common/fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ mod console;
pub use console::{ConsoleFmt, FormatSpec, console_format};

mod dynamic;
pub use dynamic::{format_token, format_token_raw, format_tokens, format_tokens_raw, parse_tokens};
pub use dynamic::{
format_token, format_token_raw, format_tokens, format_tokens_raw, parse_tokens,
serialize_value_as_json,
};

mod exp;
pub use exp::{format_int_exp, format_uint_exp, to_exp_notation};
Expand Down
Loading