feat(cast): bring remaining --json outputs into JsonEnvelope#14918
feat(cast): bring remaining --json outputs into JsonEnvelope#14918mablr wants to merge 19 commits into
--json outputs into JsonEnvelope#14918Conversation
Move `print_scalar`, `print_list`, `print_tokens`, and
`print_json_object` from
local helpers in `args.rs` into `foundry_cli::json` so every cmd module
can import them directly instead of inlining the if/else pattern.
Replace all remaining inline `if shell::is_json() { sh_println!(...) }`
patterns across cmd modules:
- `estimate`, `find_block`, `call`, `mktx` → `print_scalar`
- `txpool` (all variants), `rpc`, `vaddr/create` → `print_json_object`
- `keychain list/inspect/doctor` → `print_json_object` /
`print_json_success`
- `erc20 name/symbol/decimals` → `print_scalar`
- `erc20 balance/allowance/total_supply` → `print_json_success` (keep
`format_uint_exp` for plain-mode display)
- `Block` and `Tx` in `args.rs` → parse pre-formatted JSON string and
wrap
- `Selectors` in `args.rs` → new `SelectorInfo` struct, envelope-wraps
array
- `AbiEncodeEvent` in `args.rs` → new `EncodedEvent{topics,data}` struct
- `interface` → wrap ABI array in envelope on stdout path
Document explicitly unsupported commands with inline comments: `send`
(N::ReceiptResponse lacks Serialize), `logs --subscribe` (streaming),
`wallet` (follow-up), `upload-signature` (external API),
`four-byte-calldata` (interactive).
Add CLI tests: `selectors_json_envelope`,
`abi_encode_event_json_envelope`.
mattsse
left a comment
There was a problem hiding this comment.
Requesting changes: these paths can fail or emit invalid JSON for valid --json usages.
P1: cast call --json reparses response as JSON, but Cast::call returns raw hex when no output is decoded. Valid calls can therefore error on serde_json::from_str. Please avoid reparsing arbitrary display output; return structured data from Cast::call, or wrap raw hex as a JSON string/envelope in the command layer.
P1: cast block --json and cast tx --json also assume output is a JSON object. --raw or scalar field paths such as block hash / tx from can return scalar strings, which either fail JSON parsing or are not objects. Please only call print_json_object for object output, and use a scalar JSON envelope/string path for field selections.
P2: cast mktx prints the expiration status line to stdout before later JSON/scalar output, which corrupts --json output. Please move this to stderr/warning output, suppress it under shell::is_json(), or include it inside the JSON payload.
Please add regressions for cast call --json with undecoded/raw output, cast block/tx --json scalar field paths, and cast mktx --json --expires-at ... producing a single valid JSON document.
959137a to
60cfa08
Compare
figtracer
left a comment
There was a problem hiding this comment.
Three focused JSON-output regressions from the local review pass.
- args.rs: use get(pos).cloned() instead of indexing resolve_results to avoid panic in cast selectors --json when --resolve is not set - interface.rs: use sh_status! for "Saved interface at" so it goes to stderr in JSON mode, keeping stdout clean - keychain.rs: move is_json() branch before the is_empty() guard so cast --json keychain list emits [] instead of plain text when empty - vaddr/create.rs: defer JSON/text output until after register() succeeds to avoid emitting two stdout documents when registration fails
There was a problem hiding this comment.
crates/cast/src/cmd/vaddr/create.rs — mined salt is lost on register failure
The non---no-register path now calls register(...).await? before printing the salt/registration_hash/master_id/virtual_addresses block. On master those values were printed first.
Reproduced with no signer (so register errors):
master
STDOUT:
Salt: 0x9bf4…155f
Registration hash: 0x0000…e5fd
Master ID: 0x3a96b610
Virtual addresses:
tag=0x000000000000 0x3A96B610…0000
STDERR:
Error: cast vaddr create requires a signer (...)
PR
STDOUT: (only the "Mining…/Found salt in…" lines)
STDERR:
Error: cast vaddr create requires a signer (...)
The mined salt is now lost on any registration failure — the user has to mine again (which can take minutes). Suggest restoring the prior order: compute the payload once, print it, then call register. That also lets you collapse the duplicated JSON/plain-text blocks between the no_register branch and the register branch (both currently emit the same payload; print_json_success and print_json_object are equivalent inside if shell::is_json()).
crates/cast/src/cmd/keychain.rs — run_list empty case becomes an error
run_list now routes the empty-keys case through sh_err!, which writes to stderr and prefixes Error:. The same situation in run_show still uses sh_println!, so the two siblings disagree.
Reproduced with TEMPO_HOME pointing at a keys.toml containing keys = []:
master: EXIT 0 STDOUT: "No keys found in keys.toml." STDERR: (empty)
PR: EXIT 0 STDOUT: (empty) STDERR: "Error: No keys found in keys.toml."
An empty list is a successful result, not an error. Suggest keeping sh_println! here for parity with run_show (or change both).
crates/cast/src/cmd/interface.rs — --json stdout and --json -o file shapes diverge
cast interface --json to stdout now wraps the parsed ABIs in a JsonEnvelope, but cast interface --json -o <file> still writes the raw concatenated json_abi strings to the file. Same data, two shapes depending on whether -o is set.
Reproduced with a single-ABI input:
stdout (--json) |
file (--json -o) |
|
|---|---|---|
| master | [ { ...abi... } ] |
[ { ...abi... } ] |
| PR | {"schema_version":1,"success":true,"data":[[{...}]],…} |
[ { ...abi... } ] |
If file output is intentionally a raw artifact (not wrapped), please call that out (doc/comment + test); otherwise both destinations should share the envelope shape. Note that for multiple interfaces the file content is also several JSON objects newline-joined, which isn't one valid JSON document.
grandizzy
left a comment
There was a problem hiding this comment.
A few things to address before merge, plus some smaller suggestions.
Blocking
1. cast block/cast tx with --raw or --field will double-encode JSON in --json mode
In args.rs, only the default whole-object branch parses output to Value before wrapping:
if shell::is_json() && !is_raw_block && !has_fields {
print_json_object(serde_json::from_str::<Value>(&output)?)?;
} else {
print_scalar(output)?;
}That means cast tx --json --field authorizationList, cast block --json --raw, and any field whose value is itself an object/array will emit:
{ "data": "{\"some\":\"json\"}" }instead of a real nested object. Suggest a small helper that tries serde_json::from_str::<Value> and falls back to print_scalar on parse error, and using it for both Block and Tx. Worth adding tests for --raw and an object-valued --field.
2. keychain doctor --json drops the payload's own schema_version
The envelope's schema_version isn't the same thing as the doctor report schema — anything keying off the inner version will silently break. Please keep schema_version inside data alongside context/steps.
3. Build hygiene
interface.rsuses bareValueinserde_json::from_str::<Value>(&iface.json_abi)— I don't see ause serde_json::Value;added. Either add the import or useserde_json::Valueconsistently with the rest of the file.foundry_cli::jsonnow importsalloy_dyn_abi::DynSolValue. Iffoundry-cli'sCargo.tomldidn't already listalloy-dyn-abias a direct dep, please add it rather than relying on a transitive.
Non-blocking suggestions
- Naming:
print_json_objectis also used for arrays and scalars —print_structuredorprint_json_valuewould describe it better. Easier to rename now than later. cast rpcplain mode: the new path always parses and pretty-prints viaprint_json_object, which changes non-JSON stdout formatting too. If that's intentional, ignore; otherwise keep the oldsh_println!("{result}")branch for!is_json().mktx --expires-at: moving the "Transaction expires at..." line fromsh_println!tosh_status!cleans up--jsonstdout (good) but also changes plain-mode stdout. Consider branching onis_json()if plain-mode parity matters.interface --json: wrapping always in an array means single-interface output gains a level of nesting compared to before — worth an explicit callout in the PR description's breaking-change list.vaddr create --json: the reorder so registration runs before printing is a nice fix. Worth confirmingregister(...)itself doesn't print to stdout, and adding a JSON test for the with-register path (not just--no-register).SelectorInfo:argumentsas a comma-separated string preserves human output but an array (["uint256","address"]) would be friendlier for machine consumers. Also considerstateMutabilityto match Solidity ABI casing. Non-blocking since this is a new shape and you can iterate.
Suggested additional tests
cast block --json(default +--raw+ scalar field + object field)cast tx --json --field authorizationList(or any non-scalar field)cast rpc --jsonagainst a scalar response likeeth_blockNumbercast keychain doctor --jsonassertingdata.schema_versioncast interface --jsonfor a single ABI (locks in the array shape)cast vaddr create --jsonwithout--no-register
There was a problem hiding this comment.
@grandizzy I addressed your comment in fefa399.
I think 2. is not necessary, I just added a test to assert json shape as recommended.
Description
Towards OSS-239 (almost there after
walletfollow-up)Move
print_scalar,print_list,print_tokens, andprint_json_objectfrom local helpers inargs.rsintofoundry_cli::jsonso every cmd module can import them directly instead of inlining the if/else pattern.Replace all remaining inline
if shell::is_json() { sh_println!(...) }patterns across cmd modules:estimate,find_block,call,mktx→print_scalartxpool(all variants),rpc,vaddr/create→print_json_objectkeychain list/inspect/doctor→print_json_object/print_json_successerc20 name/symbol/decimals→print_scalarerc20 balance/allowance/total_supply→print_json_success(keepformat_uint_expfor plain-mode display)BlockandTxinargs.rs→ parse pre-formatted JSON string and wrapSelectorsinargs.rs→ newSelectorInfostruct, envelope-wraps arrayAbiEncodeEventinargs.rs→ newEncodedEvent{topics,data}structinterface→ wrap ABI array in envelope on stdout pathDocumented explicitly unsupported commands with inline comments:
send(N::ReceiptResponse lacks Serialize),logs --subscribe(streaming),wallet(follow-up),upload-signature(external API),four-byte-calldata(interactive).PR Checklist