ops: solidity-ibc-eureka v2 → v3 upgrade + SP1 v6.1 (Ethereum mainnet) - #20
Merged
Merged
Conversation
Entire-Checkpoint: 7f21cc17edda
- vm.writeJson does not resolve bracket-notation paths, so the with-sp1 rehearsal wrote junk top-level keys instead of updating the migrated light-client implementations in the shadow deployment JSON. Use dot notation like DeploySP1ICS07Tendermint already does. - Fund the ID customizer on the fork; vm.deal only covers simulation, so the addIBCApp broadcast failed on mainnet forks where the account holds no ETH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: ad70b2393387
- Shadow rehearsal: assert SP1 deployment/migration counts unconditionally so a core-only run proves no client was silently migrated - Require broadcast sender to hold ADMIN_ROLE in role-management scripts, with a pointer to the timelock-* recipes when the admin is a timelock - Bounds-check GRANT_ROLE/REVOKE_ROLE before casting to uint64 - Rename GrantPortCustomizerRole to GrantIdCustomizerRole - Delete unreferenced DeployProxiedICS26Router/DeployProxiedICS20Transfer - VerifyDeployment: actionable error when ICS27GMP is not yet registered - Runbook: document red verify CI window, RATE_LIMITER_ROLE scope change - Uniform deployment path building and forge fmt across touched scripts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 6ed894de6256
Execute the v2->v3 upgrade (core proxy/beacon upgrades + SP1 client migrations) as one atomic transaction via a Safe MultiSend (MultiSendCallOnly delegatecall through the timelock), eliminating the mixed v2/v3 window the sequential per-operation execute created. This is now the only v3 execute path. - safe.just: add execute-v3-upgrade-multisend (builds the MultiSend payload + signer safeTxHash); consolidate the SafeTxStruct hashing into one operation-aware _get_safe_message_hash. - test/SafeMultiSendV3Upgrade.t.sol: self-contained test with a real Safe + MultiSendCallOnly + TimelockController proving atomic all-or-nothing execution and predecessor ordering. - package.json / remappings.txt / foundry.toml: add safe-smart-account v1.4.1 (dev) with a via_ir=false compile profile scoped to the Safe contracts. - scripts/shadow-v2-to-v3-timelock-rehearsal.sh + shadow.just: rehearsal drives the real Safe via execTransaction + MultiSend; sequential execute path removed. - runbooks/upgrade-v2-to-v3.md: document the atomic execute as the single upgrade path. - deployments/testnet/11155111.json: refresh hub-testnet-0 + ledger-testnet-1 trusted state from the proof-api for the SP1 migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: da20a4fe7f69
…ire up pause recipes - T37: use OpenZeppelin SafeCast.toUint64 for GRANT_ROLE / REVOKE_ROLE, dropping the manual bounds-check and the unsafe-typecast lint suppression. - T36: rename the fresh-deploy helpers to a symmetric _deployICS26Stack / _deployICS20Stack / _deployICS27Stack trio; rename the shared base deployer to _deployICS27GmpContracts to avoid the name/overload clash. - pause: populate pause.just with ops-pause-transfers / ops-unpause-transfers (via the ops-script helper), import it in the justfile, and point runbooks/pause.md at the real command instead of the non-existent `just pause-deployment`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 539ea7645d05
Entire-Checkpoint: e4485fe8a821
Operations are normally branched from main, but during the v3 transition the upgrade tooling/runbook can't be on main until mainnet is upgraded (CI gate). Add an optional `base` arg (default main) so the operation can be cut from the v3-upgrade branch instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: a391af2fc4f1
Entire-Checkpoint: 7bebbd4ade24
… MultiSend - safe.just / scripts/safe-propose.sh: propose timelock schedules to the Safe Transaction Service (auto-queued nonce, owner-signed); propose-schedule wraps the schedule-v3-* recipes. - safe.just execute-timelock-multisend: pack any list of already-scheduled timelock ops into one atomic Safe MultiSend (operation=1). execute-v3-upgrade-multisend now delegates to it with the v3 upgrade as the default set + an EXTRA_TIMELOCK_OPS hook, so rate-limiter/role grants can be folded into a single timelock round on mainnet. - sp1.just / scripts/check-relayer-vkeys.sh: verify the running proof-api serves the vkeys recorded in the deployment JSON; decode_create_client.py now also emits vkeys/verifier. - runbook: propose-via-CLI flow, relayer-vkey gate, rate-limiter snapshot how-to, one-round folding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: c4a3b1bf0856
…1 clients Live Sepolia (11155111) deploy state from the v2->v3 operation: .accessManager + .ics27Gmp, the four v3 implementations, and the new SP1ICS07Tendermint clients (hub-testnet-0, ledger-testnet-1) with the v6.1 vkeys/verifier and freshly fetched trusted state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 30a9d5c965a0
…ehearsal Adds opt-in (REHEARSE_RATE_LIMITER_GRANT=1, default off) coverage of the one-round folding path the v3 atomic execute now supports: schedules a representative rate-limiter grant (AccessManager.multicall([setTargetFunctionRole, grantRole]), byte-identical to GrantRateLimiterRole.sol), folds its execute into the atomic Safe MultiSend via EXTRA_TIMELOCK_OPS (the production raw-0x mechanism), and asserts post-execute that getTargetFunctionRole + hasRole reflect the grant — with an absent pre-check so the assertion can't trivially pass. Core rehearsal is byte-for-byte unchanged when off. Runbook: document REHEARSE_RATE_LIMITER_GRANT=1 to rehearse the fold before a mainnet run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 5a147e0802d2
…ete)
Final on-chain step of the v2->v3 + SP1 v6.1 operation: addIBCApp("gmpport",
ICS27GMP) on the upgraded ICS26Router, sent by the ID customizer. After this,
verify-deployment passes all sections (ICS26/ICS20/ICS27, escrows, both SP1
clients) and check-sp1-verifier confirms v6.1 routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 9ad39fcc680c
From the live testnet run: register-ics27-gmp must be sent from an .accessManagerRoles.idCustomizers account (a Ledger here), not the deployer key. Add a Signing note (PRIVATE_KEY vs --ledger/SENDER/MNEMONIC_INDEX, blind signing) and spell out the role-gated sender in step 8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 8996768b1b43
Full testnet v2->v3 + SP1 v6.1 execution record: addresses, tx hashes, timeline, tooling, verification status, findings, and mainnet TODOs. Temporary working note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: dbe968349ee4
…ady)
Adds an independent, deployment-JSON-driven on-chain validator and a
post-upgrade AccessManager role-testing plan, executed green on Sepolia.
scripts/validate-v3-roles.py
- Verifies (A) every (target,selector)->role, (B) role membership exactly
matches the JSON (reconstructed from RoleGranted/RoleRevoked events, so it
catches missing AND stray holders + non-zero execution delays), (C) all
proxies + escrows point authority() at the AccessManager, (D) reports the
escrow setRateLimit wiring (upstream TODO #559). Trusts no deploy artifact.
- Guards against running pre-cutover (zero accessManager). Sepolia: 32/32.
runbooks/post-upgrade-role-testing.md
- Tiered method (Simulation / Proven-in-prod / Live round-trip / Fork-only),
per-role gate-simulation matrix, live grant/revoke + pause/unpause +
rate-limiter procedures, revert-selector reference, sign-off checklist.
- Testnet execution record (2026-06-16): all 8 roles confirmed executable,
all non-holders rejected, deployment restored to its exact baseline.
- Mainnet adaptation: documents the on-chain reality that mainnet cannot be
driven autonomously (4-of-7 Safe 0x7B96CD54, 72 h timelock 0xb3999B2D,
AccessManager not yet deployed) and the adapted post-cutover scope.
runbooks/upgrade-v2-to-v3.md
- Adds step 13 pointing at the role validator + testing runbook.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 6e7cb0a9278f
Answers "how do we find all the role grants for the mainnet round": the
bootstrap only grants what's in the deployment JSON, but the live v2 model is
richer, so we need to enumerate ground truth and reconcile.
scripts/discover-v2-roles.py
- For each live v2 contract (ICS26Router, ICS20Transfer, each Escrow): reads the
full RoleGranted/RoleRevoked history (Etherscan logs API, no 50k-block cap),
reconstructs current holders, confirms each with hasRole, self-discovers role
names via on-chain getters, and classifies per-client LIGHT_CLIENT_MIGRATOR
roles via their grant tx. Reconciles against deployments/<env>/<chain>.json +
the v3 role model, flagging holders that would be dropped, rate limiters to
re-grant, and roles with no v3 equivalent. Read-only.
Live mainnet (chain 1, pre-upgrade) findings, now documented:
- The 6 bootstrap-migrated roles MATCH the JSON exactly.
- RATE_LIMITER has real holders (0x4b46ea82, 0x64259f72 on the cosmoshub-0 and
ledger-mainnet-1 escrows) NOT in the JSON -> must be re-granted (step 10);
unlike testnet, this is not a no-op.
- TOKEN_OPERATOR + 11 per-client LIGHT_CLIENT_MIGRATOR roles have no v3
equivalent (dropped by design; governance-held only) -- confirm intended.
- DEFAULT_ADMIN is the timelock -> becomes v3 ADMIN.
runbooks: document the discovery step + findings table in
post-upgrade-role-testing.md (Mainnet adaptation); point the v2->v3 step-7
snapshot and step-10 re-grant at the tool with the concrete mainnet holders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 8c35d7d98884
…tomERC20 The "dropped — confirm intended" note undersold the v3 story. TOKEN_OPERATOR_ROLE in v2 only gated grant/revokeMetadataCustomizerRole on IBCERC20 (which in turn gated setMetadata) — i.e. it controlled who may relabel IBCERC20 token metadata. v3 removes mutable IBCERC20 metadata entirely; the comparable capability is the custom-ERC20 flow (setCustomERC20 under ERC20_CUSTOMIZER, which IS migrated to the same 0x4b46ea82 holder). Only in-place relabel of an auto-deployed token is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 51b12eb3f290
Replaces the earlier hedge ("confirm it survives / re-register") with the verified
conclusion. Metadata customization WAS used on mainnet (5 of 13 auto-deployed
tokens carry custom name/symbol/decimals — Cosmos Hub ATOM, SEDA, Nillion, two
hub-testnet ATOMs) and it survives the v2->v3 IBCERC20 beacon upgrade unchanged
(identical IBCERC20_STORAGE_SLOT + struct; proxies are never re-initialized; v3
just freezes it) — no action needed. Adds the enumeration caveat that
setCustomERC20 registers external tokens with no event, so the registered-denom
set is 13 beacon + 6 external = 19, not 13. Kept proportionate (informational).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 23916e2df34f
…iters discover-v2-roles.py: etherscan_logs() previously treated any non-list result as end-of-logs, so an Etherscan throttle (result is a string, e.g. "Max rate limit reached") silently returned a partial/empty set while still printing "JSON matches live v2 holders" — which could drop the RATE_LIMITER re-grant set. Now distinguishes a legitimate "No records found" from a throttle/error, retries with backoff, and exits non-zero rather than under-reporting holders. validate-v3-roles.py: RATE_LIMITER(5) now reads an optional .accessManagerRoles.rateLimiters array so the post-step-10 mainnet run (where role 5 is non-empty) matches instead of flagging live holders as UNEXPECTED; absent key -> empty -> pre-step-10/testnet behavior unchanged (still 32/32). Also corrects the docstring: the (target,selector)->role table is hand-encoded and cross-checked against IBCRolesLib + the v3 deploy wiring, not pulled from compiled methodIdentifiers as the old comment claimed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: c83777179889
- New runbooks/operations/2026-06-15-upgrade-v2-to-v3/RECORD.md: durable home for the testnet execution record (addresses, tx hashes, Safe nonces, SP1 v6.1 details), the confirmed mainnet pre-upgrade state, the authoritative mainnet grant-set/token audit, findings, and a mainnet-execution placeholder — so the TMP_WHAT_HAS_BEEN_DONE.md content survives its removal before merge. - RUNBOOK.md in the operation folder -> thin pointer to the canonical procedure + RECORD (it was a stale point-in-time copy); justfile new-operation gets a root-cause note so future operations don't re-diverge. - README: runbooks index + operations-log pointer + the discover/validate scripts. - upgrade-escrow.md / upgrade-ibcerc20.md: replace the non-existent `decode-*-upgrade` signer-verification recipe with the real `get_safe_hashes` (correct arity) and fix the duplicated step numbering. - upgrade-v2-to-v3.md: mainnet JSON-is-pre-v6.1 note + verifier trio; --version before --write; SRC_CHAIN-is-a-module-id warning; 4-of-7 hardware delegatecall proposal path; client_id<->schedule reconciliation; Ledger-Live path caveat; rate-limiter snapshot points to RECORD. - post-upgrade-role-testing.md: judge mainnet runs by `0 failed` (count scales with escrow count: testnet 2 -> 32, mainnet 3 -> ~33); role-5 non-empty post-step-10; mainnet validate prereqs (AccessManager + ics27Gmp populated, explicit FROM_BLOCK); repoint the TMP link to RECORD. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 7722e74b9116
bun >=1.2 (repo uses 1.3.14) resolves from the text bun.lock (solidity-v3.0.1 / 04b9767) and ignores the legacy binary bun.lockb, which still pinned v2.0.0. Drop the dead lockfile so nothing can resolve the old pin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: cc66b71ce41e
- safe-propose.sh: add a Ledger signing branch (LEDGER=1/--ledger, MNEMONIC_INDEX, MNEMONIC_DERIVATION_PATH) that blind-signs the EIP-712 safeTxHash; takes precedence over PRIVATE_KEY and is inherited by the propose-schedule/safe-propose recipes through the env. PRIVATE_KEY path is byte-for-byte unchanged. - safe.just (execute-timelock-multisend): before packing, verify every sub-op is a pending TimelockController operation on-chain (isOperationPending) so a mistyped/unscheduled client id or a byte-mismatched folded grant aborts the build instead of silently producing a shorter-but-valid MultiSend. Uses Pending (not Ready) so it still passes while previewing during the delay window; skipped with a warning if no RPC is configured. - RevokeRole.sol: refuse REVOKE_ROLE=0 (ADMIN) unless ALLOW_REVOKE_ADMIN=true, so a fat-fingered admin revoke cannot brick governance. Entire-Checkpoint: 7c790b340c9a
Record the resolved pre-mainnet decisions and the now-implemented tooling: - client-4 (08-wasm-301) DROPPED (no prod relayer module); migrate set = cosmoshub-0 + ledger-mainnet-1. - sp1-programs final v2.0.0 cut at the rc.2 commit -> vkeys byte-identical. - SRC_CHAIN module ids pinned from prod relayer config: cosmoshub-0<-cosmoshub-4, ledger-mainnet-1<-ledger-mainnet-1; proof-api via localhost. - ID/ERC20 customizer 0x4b46ea82 is a 2-of-5 Safe (verified on-chain), so step 8 (addIBCApp) is a Safe CALL tx, not a Ledger broadcast (corrects the prior step-8 note). - governance-Safe proposer signs with a Ledger at MNEMONIC_INDEX=1. - document the ledger proposer branch and the timelock-pending / admin-revoke guards landed in the companion commit. Entire-Checkpoint: 718da83517b7
….md) Continues from TMP_WHAT_HAS_BEEN_DONE.md (testnet) at its "What remains for mainnet" handoff: confirmed mainnet pre-upgrade state, the locked decisions (client-4 dropped, sp1-programs v2.0.0 at the rc.2 commit, SRC_CHAIN ids, 2-of-5 customizer Safe, Ledger proposer), the mainnet-specific tooling and hardening, the green mainnet-fork rehearsal, the single-round cutover plan, and the remaining checklist. Temp working note, to be removed before merge. Entire-Checkpoint: 4c84c00513e8
The timelock rehearsal registered ICS27GMP with an impersonated single-sender forge broadcast, so the production mainnet step-8 path — a 2-of-5 customizer-Safe execTransaction (addIBCApp is gated by ID_CUSTOMIZER_ROLE, held by the 2-of-5 Safe 0x4b46ea82…) — was never exercised. Add a generic safe_exec_tx() helper (the same approveHash + prevalidated-signature machinery execute_atomic already uses, generalized over to/data/operation) and a register_ics27() dispatcher: if the customizer has code (a Safe), drive addIBCApp via the real execTransaction CALL; if it's an EOA (testnet Ledger), keep the single-sender broadcast. Asserts getIBCApp(gmpport) == the ICS27 proxy afterwards. execute_atomic is left untouched (additive only). Verified end-to-end on a mainnet fork (2026-06-18): the 2-of-5 customizer Safe execTransaction lands, getIBCApp(gmpport) == the ICS27 proxy, and VerifyDeployment passes. Record the result in RECORD #8 and the runsheet T-minus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 8dac2e162e6c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: de80ce427ecd
Phase A executed on mainnet (chain 1, 2026-06-18) and verified on-chain: - deployments/mainnet/1.json: v3 AccessManager + ICS27GMP, 4 v3 impls, and SP1 v6.1 clients (cosmoshub-0, ledger-mainnet-1) with fresh trusted state, v6.1 vkeys, and gateway verifier. - Rate-limiter re-grant set: drop 0x64259f72 (decision #9) -> 2 grants, an 8-sub-call atomic fold; --expect-subcalls 8. Propagated across RECORD, CUTOVER-RUNSHEET, SIGNER-CHECKLIST, FINAL-READINESS-REPORT, upgrade-v2-to-v3, post-upgrade-role-testing, signer-verify.sh, and the shadow rehearsal. - Phase-A Foundry broadcast artifacts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: a2d4807b6141
The owner-only check (getOwners) hard-failed for the mainnet gov-Safe proposer 0x5622612b, which is a registered tx-service DELEGATE (label 'Gjermund Proposer', delegator 0x64ACC525), not one of the 7 owners. Testnet never hit this (its 1-of-2 Safe proposer was an owner). Now: a non-owner sender is allowed iff it is a registered delegate of the Safe (queried from /api/v2/delegates), failing closed if the lookup can't be made. Delegates may propose but never count toward threshold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 807903c54660
…onces 18-25) All 8 safeTxHashes independently confirmed via the gov Safe getTransactionHash and present in the tx-service queue. Includes the 2 rate-limiter grant execute blobs as the Phase-C EXTRA_TIMELOCK_OPS (holder 0x4b46ea82; 0x64259f72 dropped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 115e0e7c2bb8
Replaces the ‹N›..‹N+7› placeholders with the actual proposed nonces and their on-chain-confirmed safeTxHashes. Step-7 execute pinned to nonce 26 (conditional) and the addIBCApp row left for Phase D. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 0ceacd40859e
…creen Adversarial multi-persona + consistency review (gov/Ledger, WSL, customizer, attacker, consistency) surfaced 8 blockers + 13 should-fix; all folded in as one-line imperatives, then the doc rewritten 190->89 lines so the per-tx ceremony (DO-NOT-SIGN box + steps 1-5) fits one screen and setup/manual mode move to "do once". Security-relevant fixes: - manual mode: --expect MUST come from the table, never the Safe UI (a UI hash always self-matches -> proves nothing). - duplicate-nonce: sign only on a single PASS AND exit 0; a STOP beside a PASS still means stop (delegates can now propose, so same-nonce collisions exist). - stamp the frozen script's sha256 (7158537e...5bb0); add a second-source cross-check; Ledger blind-signing; full WSL path (CRLF/file/Ctrl+Shift+V); network-pinned Safe links; reject-on-device is safe. Verified: sha256 == scripts/signer-verify.sh; all 8 safeTxHashes byte-identical to COORDINATOR-HASH-TABLE.md; script run live against nonces 18-25 -> PASS/exit-0 with matching hashes. Frozen script untouched. Phase C/D rows remain placeholders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 465623492588
Signers only validate what they sign in the current round, so SIGNER-CHECKLIST.md is now the schedule round only — governance signers, the 8 schedules (nonces 18-25), one Safe hardcoded into the command. Dropped the two-signer framing, the execute (DELEGATECALL / --expect-subcalls 8), and the customizer step. The execute and customizer signer wording moves to SIGNER-CHECKLIST-LATER-ROUNDS.md (marked NOT for distribution), reusing the schedule-round setup + procedure, ready to hand out as its own one-round doc when each round opens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 7af96037461b
…efs) Rename runbooks/operations/2026-06-15-upgrade-v2-to-v3/ -> 2026-06-18-... and update every operation-identifier reference (path links + "Branch:" labels) in README.md, justfile, upgrade-v2-to-v3.md, post-upgrade-role-testing.md, and the operation's own RECORD / readiness docs. Historical event dates (06-16/06-17 rehearsals, decisions, on-chain snapshots) are left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 54f689428862
…e + signer) Trim this branch to the upgrade code + the current-round signer artifacts. Remove the operation's working/narrative docs (they live on gjermund/v2-to-v3-later-rounds): RECORD, READINESS-REVIEW, FINAL-READINESS-REPORT, REVIEW-RESPONSE, RUNBOOK, CUTOVER-RUNSHEET, and SIGNER-CHECKLIST-LATER-ROUNDS. All code stays (solidity, broadcasts, deployments incl. mainnet/1.json, tests, CI, foundry, just recipes, env example), as do the repo runbooks and README. Fixed the two user-facing links the removals broke: SIGNER-CHECKLIST footer (dropped the CUTOVER-RUNSHEET pointer) and README operations-log (repointed RECORD -> SIGNER-CHECKLIST). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1caabae8e099
…oads from the table signer-verify.sh no longer calls the Safe transaction service (the fetch was only convenience and added curl/jq/API-key deps while contributing nothing to trust — the anchor was always the published hash + the Ledger). It is now a pure offline generator: given a nonce it reads that tx's canonical payload from COORDINATOR-HASH-TABLE.md, recomputes the EIP-712 hashes, decodes the action, and prints the exact card a signer must confirm in the Safe UI + Ledger. Needs only `cast` — no network, no jq. - COORDINATOR-HASH-TABLE.md gains a machine-readable per-nonce payloads block (nonce|safe|to|operation|safeTxHash|data); every data recomputes to the published safeTxHash (verified, and those match the on-chain getTransactionHash). - New signer command is just `signer-verify.sh <nonce>`; manual mode (paste row fields FROM THE TABLE) remains as a fallback. Closes the manual-mode tautology (payload no longer sourced from the UI). - SIGNER-CHECKLIST.md reworked for the offline flow; publishes sha256 for BOTH the script (b3f66119…) and the table (59bcf738…) — the table now drives verification, so its integrity matters as much as the script's. Verified: all 8 nonces (18-25) generate PASS offline with matching hashes; tampered data or tampered published hash -> REJECT; unknown nonce -> no row; default table discovery (co-located files) works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 57670459e7d7
…rop the download ceremony Signers check out the repo on the branch the coordinator names; that branch IS the trust anchor (git verifies file contents), so the per-file sha256 / second-source / "download both files" ceremony is gone. The substantive protection remains the card-vs-Safe-UI-vs-Ledger comparison. - signer-verify.sh auto-discovers the operation's COORDINATOR-HASH-TABLE.md under the repo (<repo>/runbooks/operations/*/), so the command is just `bash scripts/signer-verify.sh <nonce>` from the repo root. --table still overrides. - Tolerate CRLF in the table (Windows `git checkout`), and add .gitattributes (`*.sh` + the table = eol=lf) so scripts stay runnable when checked out on Windows/WSL. - SIGNER-CHECKLIST.md setup collapses to: check out the named branch, install `cast`, enable Ledger blind-signing. No downloads, no sha256, no jq. Verified: all 8 nonces PASS via repo auto-discovery (from root and a subdir); tampered payload -> REJECT; no .gitattributes renormalization churn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 6d823ff3c001
Per direction, the signer doc says one thing: how to VALIDATE the transactions they're about to approve — nothing about how to sign, enabling Ledger blind signing, device setup, or platform specifics (Mac is the default; others figure it out). Removed the signing-ceremony mechanics, the Ledger-setup bullet, and the Windows/WSL notes; setup is now just "check out the named branch + install cast". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 3db96e6c1d17
The paragraphs were hard-wrapped at ~100 cols, splitting sentences across source lines. Unwrap each paragraph / list item / blockquote line to a single line (tables and code blocks unchanged). Content identical; verifier unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 30b942b87b7c
…nguage Fill the send-time blanks now that they're decided: clone URL (git@github.com:cosmos/eureka-ops.git) and branch (operations/2026-06-18-upgrade-v2-to-v3). Remove all reporting/"trusted channel"/ all-clear wording — signers know to report back; the doc just says how to validate and to not approve anything that doesn't. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: f5edb94858ef
…le table command Pre-send readiness review found two should-fixes: - signer-verify.sh didn't know upgradeEscrowTo(0xaaa2c343) / upgradeIBCERC20To(0x06ab20bc), so nonces 20/21 (the Escrow + IBCERC20 beacon upgrades) printed "(unknown selector ...)" with no impl line — alarming to a signer. Whitelist both; they now show the new beacon impl. Decode-only; the safeTxHash already bound the payload (all 8 still PASS, tamper REJECTs). - COORDINATOR-HASH-TABLE.md carried the pre-offline command (`signer-verify.sh 1 <safe> <nonce> --expect <hash>`, now errors); updated to `bash scripts/signer-verify.sh <nonce>`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 80d341d1b2c5
- SIGNER-CHECKLIST-EXECUTE.md (new): dedicated cutover-round checklist — DELEGATECALL to MultiSendCallOnly, `signer-verify.sh 26 --expect-subcalls 8`, sign-only (coordinator executes, only after the delay). - COORDINATOR-HASH-TABLE.md: nonce-26 payload row + Phase-C section (safeTxHash 0xe999dee7ac3a0a003383efb2fa45c9b8105ef8c21ab8322dcd9371173f0a637a). - SIGNER-CHECKLIST.md: cross-link the execute checklist. nonce-26 atomic execute independently verified: matches the gov Safe's on-chain getTransactionHash(26) and a current-state mainnet fork dry-run (warp +72h, execTransaction through the real Safe succeeds, VerifyDeployment passes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 1fdd0763c477
…le verify cmd The operator-facing runbook (runbooks/upgrade-v2-to-v3.md, lines 133/241/344) references these two docs, but they were dropped from this branch in 2242e96 — leaving broken links on the branch the operator executes from. Restore the current copies from the archive branch (gjermund/v2-to-v3-later-rounds @ 7a7f67d, which already carries the Phase-B-executed / Phase-C-built narrative). xref check: 0 dangling relative .md links across the runbook + both restored docs. Also fix one stale signer-verify invocation in the runsheet appendix (`~/signer-verify.sh 1 <safe> <nonce> --expect …` -> the current offline form `scripts/signer-verify.sh <nonce>` / `... 26 --expect-subcalls 8`) now that the runsheet is operator-facing on this branch. Note: the step-8a relayer-cut owner (runsheet line 217) is still a run-time placeholder to be assigned before the execute window. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 589e2d62e7cc
From the live-proposal validation workflow (2026-06-22): - Execute the nonce-26 bundle via the Safe UI / device Execute button — it auto-sorts the collected signatures ascending. A hand-assembled `cast send execTransaction` with a non-ascending blob reverts GS026 (recoverable; does not consume nonce 26). Avoid hand-assembly. - Collect 4-of-7 on the existing posted object — never re-propose; the REQUIRE_READY=1 rebuild is byte-identical and for the ready-assert only. - Minutes before Execute, re-query all 8 sub-ops (isOperationPending==true && getTimestamp==1782217319) and freeze other gov-Safe proposals: the gov Safe holds CANCELLER, so a stray 4-of-7 cancel would atomically void the bundle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: d450fab78cf5
The proposing Safe was hardcoded to .safe in the deployment JSON (the governance Safe). Add a --safe <addr> override so the same flow can propose to another Safe — e.g. the 2-of-5 customizer Safe used to register ICS27GMP (addIBCApp) in the v2->v3 cutover. Everything downstream (nonce(), domain separator, owner/delegate check, POST URL) already flows from this one value, so the override is sufficient. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: e017821645aa
The dry-run simulation output from building the rate-limiter grant timelock ops folded into the nonce-26 cutover bundle. Tracked to match the repo's existing broadcast/**/dry-run convention; no secrets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 388778e81697
Record the completed cutover: Phase C atomic execute (gov-Safe nonce 26, tx 0x65db3718, 2026-06-25 14:58:11 UTC, status 1, gas 527,847) and all of Phase D (escrow initializeV2 x3, relayer roll to v6.1, ICS27GMP register, verify-deployment + check-sp1-verifier + validate-v3-roles 35/0), each with its tx hash and timestamp. Flips status to COMPLETE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: ee299e5781e4
gjermundgaraba
marked this pull request as ready for review
June 26, 2026 13:26
|
Too many files changed for review. ( |
Move PROOF_API_FAILURE_MODE.md out of the repo root into runbooks/ — it is durable, reusable troubleshooting knowledge (SP1 /dev/shm exhaustion), not a this-op record, so it belongs alongside the other runbooks. Fix the two inbound links in the cutover runsheet to the new path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 08c07b4b24a5
These three dry-run artifacts are pure simulations (hash:None, 0 receipts) of a grant that actually executed via the Safe UI — throwaway, not templates. They move to the archive/2026-06-18-upgrade-v2-to-v3 branch to keep that operation's broadcast record complete, and off the lean PR to main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: d76483164ebc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The Ethereum mainnet (chain 1) operation that upgrades the
solidity-ibc-eurekadeployment from v2 → v3 and migrates its Cosmos light clients to SP1 v6.1. The branch carries the deploy/upgrade scripts, verification tooling, the updated on-chain deployment state, and the cutover runbook for the operation.What the full upgrade does
1. Access-control model: per-contract
AccessControl→ a sharedAccessManager.v3 retires the v2 per-contract roles in favour of one OpenZeppelin
AccessManager(0x3fa3f45a…), whose ADMIN is aTimelockController(0xb3999B2D…) with a 72-hour delay. Every privileged action now routes through the timelock behind the governance Safe.2. Core contract upgrades — in a mandatory order.
ICS20Transfer→ICS26Router→Escrowbeacon →IBCERC20beacon. TheICS20TransferandICS26Routerupgrades each callinitializeV2(accessManager)to move onto the new auth model; the order matters becauseICS26Router.initializeV2deletes the v2 admin records thatICS20Transfer.initializeV2still reads. After the beacon upgrade, each escrow proxy takes a one-timeinitializeV2().3. SP1 v6.1 light-client migration.
The Tendermint light clients move to SP1 v6.1 (sp1-programs
v2.0.0): a freshSP1ICS07Tendermintis deployed per client andICS26Router.migrateClient(...)is run inside the same timelock window (migration is nowAccessManager-controlled, not a per-client role). Migrated clients:cosmoshub-0(Cosmos Hub) andledger-mainnet-1;client-4is dropped (its escrow is still initialized). The Groth16 gateway routes to the real v6.1.0 verifier, vkeys are taken from the published release, and the prover/relayer is cut to that same build in lockstep with the on-chain migration.4. Rate limiting + ICS27 GMP.
RATE_LIMITER_ROLE(manager-wide in v3) is granted to the rate-limiter holder (0x4b46ea82…) on both migrated escrows. A new ICS27 GMP (general message-passing) IBC app is deployed (0xbebd14A6…) and registered on the router under thegmpportport.How it's applied
Everything that must take effect at cutover is scheduled in one timelock window and then executed as a single atomic Safe MultiSend, so the chain never settles in a mixed v2/v3 state:
scheduleoperations proposed via the 4-of-7 governance Safe — the 4 core upgrades, the 2 client migrations, and the 2 rate-limiter grants.execute(MultiSend) that applies all 8 at once.What's in the diff
DeployV3AccessManager,DeployV3Core, implementation deploys) and the SP1 v6.1 light-client deployer + vkey tooling.deployments/mainnet/1.json(v3AccessManager, ICS27 GMP, new implementations, migrated-client state) plus the broadcast artifacts.justrecipes, Foundry config) and supporting tests.