Skip to content

Commit 4e2a537

Browse files
authored
Add /verify-deployment-pr skill for deploy-PR review (#2922)
* add a deploy review skill * update skill
1 parent e974b2b commit 4e2a537

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

  • .claude/skills/verify-deployment-pr
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
name: verify-deployment-pr
3+
description: >-
4+
Verifies a POST-EXECUTION mainnet (or other network) smart-contract deployment
5+
PR for this repo: confirms every deployed contract is listed in the PR
6+
description, that the on-chain verified source (and its dependencies) matches the
7+
codebase via `sol2uml diff`, that constructor args and the initialize tx match the
8+
deploy file, and that the on-chain governance proposal matches the deploy script's
9+
actions. Use when asked to review, verify, audit, or sign off on an executed
10+
deployment PR. Invoke explicitly with /verify-deployment-pr <PR#>.
11+
argument-hint: <PR#>
12+
allowed-tools: Bash, Read, Grep, Glob, Task
13+
disable-model-invocation: false
14+
---
15+
16+
# Verify Deployment PR
17+
18+
READ-ONLY audit of an already-executed deployment PR. Never broadcast a transaction,
19+
never edit files, never re-run the live deployment. All on-chain data comes from the
20+
block explorer (via `sol2uml`/Etherscan) or a read-only RPC.
21+
22+
## Step 0 — Prerequisites (fail fast)
23+
24+
1. All commands run from `contracts/`: `cd contracts`.
25+
2. Require `contracts/.env` with `PROVIDER_URL` and `ETHERSCAN_API_KEY`:
26+
`grep -oE '^(PROVIDER_URL|ETHERSCAN_API_KEY)=' .env`. Source it for shell use:
27+
`set -a; . ./.env; set +a` (so `$ETHERSCAN_API_KEY` is available to `sol2uml`).
28+
- Missing `ETHERSCAN_API_KEY` → checks 2/3/4 can't run → STOP and report the blocker.
29+
3. Require `gh` authenticated (`gh auth status`). If absent, ask the user to paste the
30+
PR body + deployed-address list rather than failing.
31+
4. Require `sol2uml` on PATH (`command -v sol2uml`). If absent: `npm i -g sol2uml`.
32+
5. **Verify you are on the correct branch.** This is the most important prereq: every
33+
check diffs the on-chain deployment against the *local* code, so a wrong branch (or a
34+
dirty tree) silently produces meaningless results. Match the checkout to the PR head:
35+
- `gh pr view "$PR" --json headRefName,state,mergeCommit`
36+
- `git rev-parse --abbrev-ref HEAD` and `git status --porcelain`
37+
- Require the current branch to equal the PR's `headRefName`, OR — if the PR is already
38+
merged — that the checkout contains its merge commit
39+
(`git merge-base --is-ancestor <mergeCommit> HEAD`).
40+
- On mismatch or a dirty working tree → **STOP**. Tell the user to `gh pr checkout <PR>`
41+
(or `git checkout <headRefName>` / `git stash`) and re-run. Do not verify against the
42+
wrong code.
43+
44+
## Step 1 — Gather PR context
45+
46+
1. `PR=$1`. `gh pr view "$PR" --json title,body,files,baseRefName,headRefName,state,url`.
47+
2. Find the changed deploy script(s): `git diff --name-only origin/master...HEAD -- 'contracts/deploy/'`
48+
(or read the PR `files`). The folder under `deploy/<network>/` gives the **network**
49+
(mainnet, base, sonic, arbitrumOne, …) — use it for `sol2uml --network <net>` and the
50+
`deployments/<network>/` artifact folder.
51+
3. From each deploy script, parse the deployed contracts: every
52+
`deployWithConfirmation("<Name>", [args])` call (and any `*Proxy` deploys). Resolve
53+
each `<Name>` → address + recorded args via `deployments/<network>/<Name>.json`
54+
(`.address`, `.args`, `.transactionHash`).
55+
4. Extract `proposalId` and `deployName` from the script's
56+
`deploymentWithGovernanceProposal({ ... })` options block, and the `actions` array.
57+
If `proposalId` is `""`/absent, mark check 5 ⚠️ ("no proposalId recorded — resolve
58+
from on-chain Governor events or ask the deployer").
59+
5. Working set: `[{name, address, recordedArgs, proposalId, network, deployScript}]`.
60+
61+
## Step 2 — Run the checks
62+
63+
Checks 2/3/4 are per-address and independent — fan them out with parallel `Task`
64+
sub-agents (one per deployed contract) when there are several; otherwise run inline.
65+
Each check yields: status (✅ pass / ⚠️ needs-human / ❌ fail), one-line evidence, a
66+
details block, and a confidence (High/Med/Low). Absence of evidence is ⚠️/❌, never ✅.
67+
68+
**1 — All deployed contracts listed in the PR description**
69+
- Compare the Step-1 deployed name+address set against the PR body.
70+
- ✅ every deployed contract (name and address) appears; ❌ a deployed contract is
71+
missing; ⚠️ names present but addresses missing/ambiguous.
72+
73+
**2 — Verified on-chain code (and dependencies) matches the codebase**
74+
- For each deployed **implementation** address, from `contracts/`:
75+
`sol2uml diff <address> .,node_modules --network <net> --apiKey "$ETHERSCAN_API_KEY"`
76+
This downloads the explorer-verified source for the address and diffs it (and its
77+
dependencies) against the local checkout.
78+
- ✅ sol2uml reports no differences (every file identical); ❌ any file differs — quote
79+
the differing files/lines. For `*Proxy` addresses run the same diff against the proxy
80+
source; expect the standard proxy to match.
81+
- Confidence High when the diff is clean/empty.
82+
83+
**3 — Constructor arguments are correct**
84+
- Read the args passed to `deployWithConfirmation("<Name>", [args])` in the deploy file.
85+
- Compare them to the recorded `deployments/<network>/<Name>.json` `.args` AND to the
86+
on-chain "Constructor Arguments" on the explorer (Etherscan contract page, or decode
87+
the tail of the creation tx input). They must match positionally.
88+
- ✅ all args match; ❌ any positional mismatch (show deploy-file value vs on-chain).
89+
Proxies typically have no constructor args — note `[]`.
90+
91+
**4 — The initialize / interaction tx matches the deploy script**
92+
- If the deploy script performs a post-deploy call — typically a proxy
93+
`initialize(...)`/`_initialize(...)` (often `withConfirmation(cProxy.connect(...).initialize(...))`
94+
or a proxy `*Proxy` deploy followed by initialize) — locate the corresponding on-chain
95+
tx (explorer tx list for the proxy address, or the proxy's deployment receipt) and
96+
confirm the **arguments match the deploy file**.
97+
- ✅ the initialize tx args match the script; ⚠️ the script has no init/interaction call
98+
(nothing to check); ❌ args differ (show the diff).
99+
100+
**5 — Governance proposal matches the deploy script**
101+
- Read the on-chain proposal directly via ethers — do NOT use `npx hardhat proposal --id`,
102+
it parses the id as a float and overflows on real (77-digit) proposalIds. Use the
103+
GovernorSix (`addresses.mainnet.GovernorSix`) ABI with the id as a `BigNumber`:
104+
`g.state(id)` and `g.getActions(id) -> (targets, values, signatures, calldatas)`
105+
(calldatas are selector-stripped — the signature is a separate string).
106+
- Build the expected actions from the deploy script's `actions` array: resolve each
107+
`action.contract` → target address, keep `signature`, and encode `args` with
108+
`defaultAbiCoder.encode(<param types>, args)` to compare against each on-chain calldata.
109+
- ✅ identical (same count, targets, signatures, calldatas); ❌ any divergence (show it).
110+
Report the proposal `state`: `Executed` for a fully-executed deploy; `Pending`/`Active`/
111+
`Queued` is normal when reviewing before execution — flag it but it is not a failure.
112+
113+
**6 — Smoke tests after fork execution** — SKIPPED (per project decision). Mark N/A.
114+
115+
## Step 3 — Synthesize
116+
117+
Verdict = **VERIFIED** only if checks 2,3,4,5 are ✅ (check 1 may be ⚠️ if the sole gap
118+
is documentation; check 4 may be ⚠️ if there is genuinely no init/interaction call).
119+
Any ❌ in 2–5, or an unresolved ⚠️, → **BLOCKERS FOUND**. Emit the report:
120+
121+
```
122+
# Deployment PR Verification — #<PR> (<title>)
123+
Verified against: <branch>@<short-sha>
124+
Deploy script(s): <list> | Network: <net> | Proposal: <proposalId>
125+
Verdict: <VERIFIED | BLOCKERS FOUND>
126+
127+
- [<✅|⚠️|❌>] 1. All deployed contracts listed in PR description — <evidence> (conf)
128+
- [<✅|⚠️|❌>] 2. Verified code (+deps) matches codebase (sol2uml diff) — <evidence> (conf)
129+
- [<✅|⚠️|❌>] 3. Constructor args correct — <evidence> (conf)
130+
- [<✅|⚠️|❌>] 4. Initialize/interaction tx matches deploy script — <evidence> (conf)
131+
- [<✅|⚠️|❌>] 5. Governance proposal matches deploy script — <evidence> (conf)
132+
- [⏭️] 6. Smoke tests — skipped (N/A)
133+
134+
## Details
135+
### 2. sol2uml diff <per-address: address, clean/diff, differing files>
136+
### 3. Constructor args <per-address: deploy-file args vs on-chain args>
137+
### 4. Initialize tx <tx hash, decoded args vs deploy-file args>
138+
### 5. Proposal diff <on-chain getActions vs script actions, or "identical">
139+
140+
## Human still owes (manual)
141+
- Off-chain Safe/multisig follow-ups noted in the deploy script (e.g. enableModule).
142+
- That the PR's stated intent matches the on-chain effect (judgment).
143+
- Anything marked ⚠️ above.
144+
```
145+
146+
## Notes / common false positives
147+
148+
- For `sol2uml diff`, run from `contracts/` and source `.env` first so `$ETHERSCAN_API_KEY`
149+
is set; use `--network` matching the deploy folder (base→`base`, sonic→`sonic`, etc.).
150+
- A **proxy address will not match an implementation's source** — diff a `*Proxy` against
151+
the proxy contract, not the impl.
152+
- A clean `sol2uml diff` (no file differences) is the pass signal for check 2; treat any
153+
reported file difference as ❌ pending human review, not a warning.
154+
- If `proposalId` is empty, do not fabricate one — mark checks 5 ⚠️ and ask the deployer.
155+
- If a helper command errors/rate-limits, retry once, then mark that check ⚠️ "tool
156+
error" with the stderr tail and continue the others. Never silently pass.
157+
158+
## Do NOT
159+
160+
- Never send transactions, never re-run the deployment, never edit files. Explorer +
161+
read-only RPC only.
162+
- Never declare VERIFIED while any of checks 2–5 is ❌ or an unresolved ⚠️.

0 commit comments

Comments
 (0)