Commit f0d6d97
authored
feat(deployer): add verify-proposal and auto-generate upgrade proposals (#4618)
* feat(deployer): add verify-proposal and auto-generate upgrade proposals
- Add `deploy verify-proposal --contract <kind> --input schedule.json --input
execute.json`: independently verify a timelock (schedule+execute) upgrade
proposal against on-chain state and shipped bytecode, without trusting
Etherscan or the JSON description
- Bytecode check: compare the on-chain impl to the contract binding's
DEPLOYED_BYTECODE, normalizing trailing solc CBOR metadata and the UUPS
__self immutable; classify FullMatch / CodeMatchMetaDiffers / Mismatch
- Governance checks: proxy owner()/currentAdmin() == mapped timelock, delay >=
getMinDelay, value/predecessor zero, init selector, version prerequisite;
assert schedule and execute share inner payload, salt, value, predecessor
- Compute Safe EIP-712 domain/message/tx hashes so signers can match their Ledger
- deploy writes proposals into contracts/deployments/proposals/<network>/<date>-<slug>/
(network from chain id; --network/--proposal-slug/--proposals-root overrides):
schedule.json, execute.json, a contract kind file, and a templated README
- Auto-fill the README Safe hashes: resolve the signing Safe from the
deployment-info toml and read its nonce on-chain; skip with a warning when the
signer set is ambiguous or lookup fails
- Add a CI job to verify committed proposals under contracts/deployments/proposals/**
* fix(deployer): verify-proposal takes a proposal directory; fix lint
- Replace the two --input files with a positional proposal directory; read
schedule.json, execute.json, and the contract kind from <dir>/contract
(--contract now optional, cross-checked against the file). Fixes the broken
num_args=2 usage that rejected two separate --input flags
- Simplify the CI job to verify-proposal "$dir"; update README template
(PROPOSAL_DIR token), docs, and the helper script comment to the dir form
- Remove unused test imports (OpsTimelock, ext::AnvilApi) that failed clippy
--all-targets -D warnings; apply nightly rustfmt import grouping
* feat(deployer): validate proposals via proposal.toml, drop per-proposal README
- Proposal PRs add only data: schedule.json, execute.json, and proposal.toml
(contract kind, per-phase safe/nonce, all six Safe hashes, and metadata). The
author is untrusted; correctness is established by verify-proposal, not by text
- verify-proposal recomputes proxy/impl/salt/delay/predecessor/hashes from the
JSONs + on-chain + shipped bytecode and asserts they match proposal.toml; the
per-phase safe is cross-checked against deployment-info; nonce drift warns
- Drop --safe/--nonce from verify (read from the toml); deploy writes the toml
(auto-resolves signers, --safe override for ambiguous networks)
- Remove per-proposal README generation and the contract file; the only README is
the static, trusted contracts/deployments/proposals/README.md describing the flow
* docs(deployer): document the 5-step proposal flow in the proposals README
* docs(deployer): tighten proposal flow to short steps
* docs(deployer): signers confirm all three hashes on the Ledger
* feat(deployer): default verify-proposal RPC to the network public node
- verify-proposal reads chain_id from proposal.toml and defaults --rpc-url to the
network's public node (1 -> mainnet, 11155111 -> sepolia publicnode); --rpc-url overrides
- Run verify read-only via its own provider, dispatched before the wallet provider
is built, so it needs no mnemonic and no localhost node
* fix(ci): make verify-proposals workflow actually run
- build with rustup/mold/protoc like build.yml instead of the nix dev shell
- run ./target/debug/deploy; --profile test outputs to target/debug, so the
old ./target/test/deploy path failed every run
- fetch-depth: 0 so git diff against the base ref resolves
- verify a proposal dir when any file in it changes; the .json-only grep
skipped proposal.toml tampering and broke under pipefail on no match
- drop the per-network RPC case; the binary resolves the public node itself
- add Swatinem/rust-cache and raise the timeout to 30 minutes
* fix(deployer): close verification bypasses in verify-proposal
- require exactly one transaction per Safe batch; extra txs escaped checks
- fail non-empty init data for kinds without an expected init selector
- embed deployment-info TOMLs via include_str!; the CWD-relative path let
checks silently skip outside the repo root
- always emit timelock-addr-match and proxy-addr-match from deployment-info;
previously skipped without contract-address env vars, letting a look-alike
proxy owned by an attacker timelock pass
- hard-fail Safe proposer/executor checks on unknown networks and cover the
SafeExit timelock instead of skipping it
- pin network name to chain id (mainnet/decaf/hoodi); the default RPC was
chosen from toml.chain_id and compared against itself
- honor ESPRESSO_L1_PROVIDER on the verify-proposal subcommand
- strip CBOR metadata only for a bounded tail ending in the canonical solc
entry; the old scan never matched real bindings and allowed oversized
attacker tails
- mask immutables at per-kind artifact offsets instead of a substring scan;
offsets kept in sync with bindings by tests over all five contract kinds
* feat(ci): post proposal verification results as a PR comment
- capture each proposal dir's verify output and PASS/FAIL status into a
markdown file, posted via marocchino/sticky-pull-request-comment like
test.yml
- four-backtick fences since proposal descriptions are untrusted
- stale comment is deleted when nothing was verified
- job gets pull-requests: write; contents stays read-only
* fix(ci): comment on the PR when verification fails before running
- build or setup failures previously posted nothing and deleted the
previous sticky comment
- write a fallback FAIL comment with the run link when the job fails
without producing pr-comment.md
* fix(deployer): offset-free bytecode check, version-aware init check
- compare bytecode by explaining every difference: accept only 20-byte
windows holding the impl address over reference zeros (UUPS __self
slots), require the exact per-contract window count, and report the
offset plus both values on mismatch
- delete the per-contract immutable offset tables; pin each binding's
zero runs to the expected slot count in tests instead
- accept empty init data only when the proxy already reached the
reinitializer's target major version; fail closed if the version
query fails
* docs(deployer): explain why LightClient verification is unsupported1 parent 26603af commit f0d6d97
15 files changed
Lines changed: 3667 additions & 11 deletions
File tree
- .github/workflows
- contracts
- deployments/proposals
- rust/deployer
- src
- proposals
- fixtures
- crates/espresso/node/src/bin
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| 25 | + | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| |||
152 | 153 | | |
153 | 154 | | |
154 | 155 | | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
155 | 165 | | |
156 | 166 | | |
157 | 167 | | |
| |||
535 | 545 | | |
536 | 546 | | |
537 | 547 | | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
545 | 577 | | |
546 | 578 | | |
547 | | - | |
| 579 | + | |
548 | 580 | | |
549 | 581 | | |
550 | 582 | | |
551 | 583 | | |
552 | | - | |
| 584 | + | |
553 | 585 | | |
554 | 586 | | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
555 | 591 | | |
556 | 592 | | |
557 | 593 | | |
| |||
0 commit comments