Skip to content

Latest commit

 

History

History
177 lines (150 loc) · 15.3 KB

File metadata and controls

177 lines (150 loc) · 15.3 KB

Output channels (stdout / stderr) — contract

This document defines the rules every Foundry CLI command (forge, cast, anvil, chisel, script, verify) must follow when writing to standard output and standard error.

Status: The macros, helpers, and routing primitives described here are implemented. The per-command stdout table at the bottom is the target contract that downstream migration PRs will bring each command into compliance with; it does not yet describe today's behavior for every command.

The contract

stdout is the command's primary result and nothing else. In text mode it is one canonical value (or one record per line, tab-separated for multi-column output). In --json mode it is a single JSON document.

stderr is every other byte the command emits: warnings, errors, progress indicators, status prose, prompts, banners, ABI dumps, verification chatter.

Corollaries

  • --json only changes stdout's format, never its channel cleanliness.
  • --quiet suppresses stderr diagnostics and progress. The target contract is that it never alters stdout content; today sh_print!/sh_println! are still suppressed by --quiet and that bypass will be flipped on once the major prose stdout call sites in forge/script have been migrated to sh_status!. sh_err! is the documented exception: fatal errors are always emitted on stderr.
  • -vvv adds stderr verbosity. It must never alter stdout content.
  • A command that has no primary result (e.g. forge install) writes nothing to stdout by default.
  • Prompts are diagnostics: write the question to stderr, read the answer from stdin.

The acceptance criterion this enables: an agent or shell script can run any command, discard stderr, and trust that stdout contains only the documented machine-readable result.

forge create … 2>/dev/null              # → contract address only
forge test --json 2>/dev/null | jq …    # → valid JSON, always
cast call … 2>/dev/null | xargs …       # → return value only

How to write code that follows the contract

Use the sh_* macros in [foundry_common::io]; do not call println! / eprintln! directly. A workspace-wide clippy disallowed-macros lint (see clippy.toml) forbids the four std::print* / std::eprint* macros everywhere; the sh_* macros expand to write! calls on the global Shell, so they are not affected by the lint.

Macro Channel Suppressed by --quiet Use for
sh_println! stdout yes (target: no, see above) The command's primary machine-readable result.
sh_print! stdout yes (target: no, see above) Same as sh_println!, no trailing newline.
sh_status! stderr yes Status prose ("Compiling…", "Deploying contract…").
sh_progress! stderr yes (also no-op when stderr ≠ tty) Spinner/progress-bar style transient updates.
sh_warn! stderr yes Recoverable problems. Adds a "Warning:" prefix.
sh_err! stderr no Errors. Adds an "Error:" prefix.
sh_eprintln! stderr yes Escape hatch for raw stderr text that doesn't fit the above.
sh_eprint! stderr yes Same as sh_eprintln!, no trailing newline.
prompt! stderr (question) + stdin (answer) yes (question is sh_eprint!) Interactive question/answer.

The compilation reporter in foundry_common::compile writes its spinner to stderr in TTY mode (see SpinnerReporter). The non-TTY fallback still writes to stdout today; flipping it to stderr will shift many existing snapshot tests and is part of the per-command migration backlog. Calling sh_progress! directly is also acceptable for one-off progress lines.

Decision rule for any sh_println! call site

For each sh_println! call you write or review, ask:

  1. Is this the canonical primary result of the command?
    • Yes → keep sh_println! (stdout).
    • No → use sh_status!, sh_warn!, sh_err!, or sh_eprintln! (stderr).
  2. Does this line mix labels with data (e.g. "Deployer: 0x…")?
    • The label is prose → move the whole line to sh_status! (stderr).
    • The data alone belongs on stdout → emit just the value (sh_println!).
    • In --json mode, both belong inside the single JSON document on stdout.

Per-command stdout contract (target)

This table is the target contract that migration PRs will bring each command into compliance with. It is the source of truth for what each command's stdout will contain after migration, not necessarily what it contains today. Each row's status is one of:

  • migrated — current behavior matches this contract.
  • todo — current behavior does not match yet; a follow-up PR is needed.

cast

Command Text mode stdout --json stdout Status
cast call Return value (hex / decoded) JSON of return value migrated
cast send Receipt (or tx hash with --async) JSON receipt (or hex tx hash with --async) migrated
cast estimate Gas estimate (decimal) JSON { "gas": "…" } migrated
cast rpc RPC result (JSON) JSON migrated
cast storage Single slot value JSON of layout migrated
cast logs Pretty-printed logs JSON array migrated
cast run Trace / decoded output JSON migrated
cast trace Trace JSON trace migrated
cast wallet new One record per wallet: address (keystore) or address\tprivate_key (no keystore) JSON array of { address, public_key, path } (keystore) or { address, public_key, private_key } (no keystore) migrated
cast wallet sign Signature JSON migrated
cast wallet sign-auth Signed authorization RLP JSON migrated
cast erc20 balance Balance (decimal) JSON string migrated
cast create2 address\tsalt (tab-separated) n/a migrated
cast access-list Access list JSON migrated
cast interface Solidity interface source JSON ABI array migrated
cast artifact JSON artifact n/a migrated
cast creation-code Hex bytecode (or disassembly with --disassemble) n/a migrated
cast constructor-args One constructor arg per line n/a migrated
cast b2e-payload JSON execution payload n/a migrated
cast tx-pool JSON JSON migrated
cast da-estimate Gas estimate JSON migrated
cast find-block Block number JSON migrated
cast mktx Signed RLP JSON migrated
cast batch-mktx Signed RLP (or unsigned RLP with --raw-unsigned) n/a migrated
cast batch-send Receipt (or tx hash with --async) JSON receipt (or hex tx hash with --async) migrated

forge

Command Text mode stdout --json stdout Status
forge build (empty) JSON build output todo
forge test (empty; exit code = pass/fail) JSON test results, JUnit XML with --junit todo
forge create Deploy: Deployer: / Deployed to: / Transaction hash: lines. Dry-run: Contract: / Transaction: / ABI: lines. Compiler output may precede (tracked under forge build). JSON { deployer, deployedTo, transactionHash } (or dry-run JSON { contract, transaction, abi }) todo
forge inspect <field> Just that field's value JSON of that field migrated
forge install (empty) (empty) migrated
forge init (empty) (empty) migrated
forge update (empty) (empty) migrated
forge remove (empty) (empty) migrated
forge clone (empty) (empty) migrated
forge bind (empty) (empty) migrated
forge bind-json (empty) or generated path JSON migrated
forge flatten Flattened source n/a migrated
forge fmt (empty) or formatted source with --check n/a migrated
forge tree Dependency tree JSON migrated
forge config Config TOML JSON config migrated
forge selectors Selectors output JSON migrated
forge eip712 (empty) JSON of types migrated
forge geiger Findings JSON migrated
forge lint (empty; findings on stderr/exit code) JSON findings migrated
forge snapshot Per-test diff lines (--diff); table (--format table); (empty) otherwise n/a migrated
forge coverage Coverage table or report JSON / LCOV / etc. via --report todo
forge cache (empty) or paths JSON migrated
forge clean (empty) n/a migrated
forge completions Generated shell completion script n/a migrated
forge doc (empty) n/a migrated
forge generate (empty) or generated path n/a migrated
forge soldeer Passthrough to soldeer crate; foundry adds no wrapper prose n/a migrated
forge remappings One remapping per line n/a migrated
forge compiler Compiler info JSON migrated
forge verify-contract <guid-or-job-id>\t<url> on submission; empty if already verified n/a migrated
forge verify-bytecode <type> code matched with status <kind> lines JSON array of { bytecode_type, match_type, message } migrated

anvil, chisel, script

Command Text mode stdout --json stdout Status
anvil Banner, accounts, RPC URL on stderr n/a todo
chisel REPL output n/a todo
forge script Simulation/broadcast result JSON todo

Commands not listed here have not been classified yet — please open an issue or PR before relying on their stdout format.

References