|
| 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