Improve simulation output UX for signers and upgrade-task authors
Summary
The console output of just simulate / just simulate-stack is hard to act on safely.
The operator-facing artifacts a signer actually needs — the data to sign, the
Tenderly simulation link, and the calldata to paste — are short sections buried
between (and split apart by) very large unbreakable hex/URL blobs, repeated per task in a
stack, and surrounded by warnings whose severity is unclear. With Tenderly's new ?draft=
link format (see #1458) the calldata is now almost always printed separately from the
link, which makes the "which hex goes with which link" problem worse in stacked runs.
This issue proposes UX improvements for two audiences:
- Signers, who must verify and sign the right hash and reproduce the simulation in Tenderly.
- Upgrade-task authors, who author/iterate on tasks and need machine-usable, trustworthy output.
It is a follow-up to #1458 (which fixed the link format); this is about the presentation
of the simulation output, which #1458 did not change.
How this was observed
Captured from a live Sepolia stacked simulation of the PR #1440 tasks
[099-U19-op-ink-mmz-soneium, 100-U19-unichain]:
stacked-simulation-output.log — 2,298 lines (SIGNING_MODE_IN_PROGRESS=false, full forge call-traces).
stacked-simulation-output-quiet.log — 151 lines (default quiet mode).
Even the 151-line quiet output is dominated by a handful of enormous single lines:
| Section |
Line length (task 099) |
TASK CALLDATA |
28,620 chars |
OP-TXVERIFY LINK |
40,028 chars |
Tenderly Simulation link: |
2,378 chars |
Insert the following hex … (raw input) |
32,332 chars |
Signer-facing pain points
-
The Tenderly link and its companion "Raw input data" hex are split and far apart.
Because real tasks exceed Tenderly's ~2000-char URL cap, the link is printed without
rawFunctionInput and the calldata is printed separately (quiet log lines 96–100).
In a stack there are N links and N hex blobs interleaved with other sections, with
no shared label tying link↔hex together. It is easy to paste the wrong hex into the
wrong simulation.
-
Only the last task in a stack is the one to sign, but every task prints a full
DATA TO SIGN + ATTENTION SIGNERS block (quiet log lines 75–87 and 124–136). In a
plain simulate-stack run both hashes print with equal prominence. A signer can sign
the wrong hash. (STACKED_SIGNING_MODE suppresses earlier ones, but that's not the
default path.)
-
Stacked links silently fail for downstream tasks, and the warning is last. Opening
the final task's link in Tenderly reverts with GS013 (inner call reverted) because the
draft link can carry Safe threshold/owner/nonce overrides but not the chain-state
mutations from earlier tasks in the stack. The only warning —
"Some Tenderly links may not work when running a stacked simulation" — is printed once,
generically, at the very end (line 151), after all the links. A signer who clicks the
last link first gets a confusing revert with no pointer.
-
Noise before signal. The useful output is preceded by a wall of
[WARN] Contract: PermissionlessWETH not found for chain: Mode Testnet /
OPCMUpgradeV700: per-chain ProtocolVersions lookup failed for chainId …
(quiet log lines 33–50) for chains unrelated to the task being signed. Nothing tells the
signer whether these are benign or actionable.
-
WELCOME TO SUPERCHAIN-OPS and the welcome/attention preambles reprint once per task
in a stack (lines 53–56, 108–111). Redundant and pushes real content down.
-
Unbreakable mega-lines. 28k–40k-char single lines wrap unreadably in a terminal and
are easy to mis-copy. The op-txverify link (40k chars) is the single largest blob and
sits between the data-to-sign and the Tenderly link.
Task-author-facing pain points
-
No machine-readable output. Everything is human-formatted console.log. Authors and
CI cannot reliably extract the link / data-to-sign / calldata without brittle scraping
(and the giant lines make even scraping awkward). A --json / structured artifact mode
would let tooling consume results directly.
-
The "embed calldata in the link" path is effectively dead for real tasks: a Safe
execTransaction bundle always exceeds 2000 chars, so the fallback (link without
calldata + separate hex) is the normal path, not the exception. Authors should know up
front that one-click links aren't achievable for standard tasks, and the tooling could
say so explicitly rather than looking like a degraded case.
-
No single combined artifact for a stack. Authors can't produce one Tenderly
simulation that represents the whole stack. The supported workaround (Tenderly Virtual
TestNets) is mentioned only in a trailing warning string, with no tooling/automation.
-
Verbosity is inconsistent across entry points. Some flows dump the full 2,298-line
forge call-trace; others the 151-line quiet view. There's no documented switch for
"give me only the operator-facing summary."
Proposed improvements (for discussion / prioritization)
High value, low effort
- Keep each Tenderly link and its "Raw input data" hex immediately adjacent, under one
clearly-labeled, per-task block (e.g. ── Tenderly simulation: 100-U19-unichain ──),
with the link, then the hex, then a one-line "paste this hex into Raw input data".
- In a stack, clearly mark which task is the one being signed and de-emphasize / collapse
the earlier tasks' DATA TO SIGN blocks (or only print the final one by default).
- Move the stacked-link caveat next to each downstream link (not just at the end), and
name the failure mode (GS013 / missing upstream state) so a confused signer can self-serve.
Medium
- Group/suppress the per-chain
[WARN] … not found / ProtocolVersions lookup failed
noise behind a verbosity flag, or summarize as a single line with a count.
- Print the welcome/attention preamble once per invocation, not once per task.
- Document the verbosity switch and make the quiet, operator-facing view the default for
simulate/simulate-stack.
Larger / strategic
- Add a structured output mode (
--json or a written artifact) containing, per task:
{ safe, domainHash, messageHash, dataToSign, tenderlyLink, rawInputHex, isLastInStack }.
Signers' tooling (op-txverify, dashboards) and CI can consume it directly.
- Investigate generating a single Tenderly Virtual TestNet (or a sequence of pre-linked
draft sims that share state) for a stack, so the final task's link actually simulates.
Appendix: evidence
Improve simulation output UX for signers and upgrade-task authors
Summary
The console output of
just simulate/just simulate-stackis hard to act on safely.The operator-facing artifacts a signer actually needs — the data to sign, the
Tenderly simulation link, and the calldata to paste — are short sections buried
between (and split apart by) very large unbreakable hex/URL blobs, repeated per task in a
stack, and surrounded by warnings whose severity is unclear. With Tenderly's new
?draft=link format (see #1458) the calldata is now almost always printed separately from the
link, which makes the "which hex goes with which link" problem worse in stacked runs.
This issue proposes UX improvements for two audiences:
It is a follow-up to #1458 (which fixed the link format); this is about the presentation
of the simulation output, which #1458 did not change.
How this was observed
Captured from a live Sepolia stacked simulation of the PR #1440 tasks
[099-U19-op-ink-mmz-soneium, 100-U19-unichain]:stacked-simulation-output.log— 2,298 lines (SIGNING_MODE_IN_PROGRESS=false, full forge call-traces).stacked-simulation-output-quiet.log— 151 lines (default quiet mode).Even the 151-line quiet output is dominated by a handful of enormous single lines:
TASK CALLDATAOP-TXVERIFY LINKSimulation link:Insert the following hex …(raw input)Signer-facing pain points
The Tenderly link and its companion "Raw input data" hex are split and far apart.
Because real tasks exceed Tenderly's ~2000-char URL cap, the link is printed without
rawFunctionInputand the calldata is printed separately (quiet log lines 96–100).In a stack there are N links and N hex blobs interleaved with other sections, with
no shared label tying link↔hex together. It is easy to paste the wrong hex into the
wrong simulation.
Only the last task in a stack is the one to sign, but every task prints a full
DATA TO SIGN+ATTENTION SIGNERSblock (quiet log lines 75–87 and 124–136). In aplain
simulate-stackrun both hashes print with equal prominence. A signer can signthe wrong hash. (
STACKED_SIGNING_MODEsuppresses earlier ones, but that's not thedefault path.)
Stacked links silently fail for downstream tasks, and the warning is last. Opening
the final task's link in Tenderly reverts with
GS013(inner call reverted) because thedraft link can carry Safe threshold/owner/nonce overrides but not the chain-state
mutations from earlier tasks in the stack. The only warning —
"Some Tenderly links may not work when running a stacked simulation" — is printed once,
generically, at the very end (line 151), after all the links. A signer who clicks the
last link first gets a confusing revert with no pointer.
Noise before signal. The useful output is preceded by a wall of
[WARN] Contract: PermissionlessWETH not found for chain: Mode Testnet/OPCMUpgradeV700: per-chain ProtocolVersions lookup failed for chainId …(quiet log lines 33–50) for chains unrelated to the task being signed. Nothing tells the
signer whether these are benign or actionable.
WELCOME TO SUPERCHAIN-OPSand the welcome/attention preambles reprint once per taskin a stack (lines 53–56, 108–111). Redundant and pushes real content down.
Unbreakable mega-lines. 28k–40k-char single lines wrap unreadably in a terminal and
are easy to mis-copy. The
op-txverifylink (40k chars) is the single largest blob andsits between the data-to-sign and the Tenderly link.
Task-author-facing pain points
No machine-readable output. Everything is human-formatted
console.log. Authors andCI cannot reliably extract the link / data-to-sign / calldata without brittle scraping
(and the giant lines make even scraping awkward). A
--json/ structured artifact modewould let tooling consume results directly.
The "embed calldata in the link" path is effectively dead for real tasks: a Safe
execTransactionbundle always exceeds 2000 chars, so the fallback (link withoutcalldata + separate hex) is the normal path, not the exception. Authors should know up
front that one-click links aren't achievable for standard tasks, and the tooling could
say so explicitly rather than looking like a degraded case.
No single combined artifact for a stack. Authors can't produce one Tenderly
simulation that represents the whole stack. The supported workaround (Tenderly Virtual
TestNets) is mentioned only in a trailing warning string, with no tooling/automation.
Verbosity is inconsistent across entry points. Some flows dump the full 2,298-line
forge call-trace; others the 151-line quiet view. There's no documented switch for
"give me only the operator-facing summary."
Proposed improvements (for discussion / prioritization)
High value, low effort
clearly-labeled, per-task block (e.g.
── Tenderly simulation: 100-U19-unichain ──),with the link, then the hex, then a one-line "paste this hex into Raw input data".
the earlier tasks'
DATA TO SIGNblocks (or only print the final one by default).name the failure mode (
GS013/ missing upstream state) so a confused signer can self-serve.Medium
[WARN] … not found/ProtocolVersions lookup failednoise behind a verbosity flag, or summarize as a single line with a count.
simulate/simulate-stack.Larger / strategic
--jsonor a written artifact) containing, per task:{ safe, domainHash, messageHash, dataToSign, tenderlyLink, rawInputHex, isLastInStack }.Signers' tooling (op-txverify, dashboards) and CI can consume it directly.
draft sims that share state) for a stack, so the final task's link actually simulates.
Appendix: evidence
stacked-simulation-output.log(verbose, 2,298 lines)stacked-simulation-output-quiet.log(quiet, 151 lines)?draft=link format)099-U19-op-ink-mmz-soneium,100-U19-unichain)