--- name: verify-deployment-pr description: >- Verifies a POST-EXECUTION mainnet (or other network) smart-contract deployment PR for this repo: confirms every deployed contract is listed in the PR description, that the on-chain verified source (and its dependencies) matches the codebase via `sol2uml diff`, that constructor args and the initialize tx match the Foundry script, and that the on-chain governance proposal matches the script's actions. Use when asked to review, verify, audit, or sign off on an executed deployment PR. Invoke explicitly with /verify-deployment-pr . argument-hint: allowed-tools: Bash, Read, Grep, Glob, Task disable-model-invocation: false --- # Verify Deployment PR READ-ONLY audit of an already-executed deployment PR. Never broadcast a transaction, never edit files, never re-run the live deployment. All on-chain data comes from the block explorer (via `sol2uml`/Etherscan) or a read-only RPC. ## Step 0 — Prerequisites (fail fast) 1. All commands run from `contracts/`: `cd contracts`. 2. Require `contracts/.env` with `PROVIDER_URL` and `ETHERSCAN_API_KEY`: `grep -oE '^(PROVIDER_URL|ETHERSCAN_API_KEY)=' .env`. Source it for shell use: `set -a; . ./.env; set +a` (so `$ETHERSCAN_API_KEY` is available to `sol2uml`). - Missing `ETHERSCAN_API_KEY` → checks 2/3/4 can't run → STOP and report the blocker. 3. Require `gh` authenticated (`gh auth status`). If absent, ask the user to paste the PR body + deployed-address list rather than failing. 4. Require `sol2uml` on PATH (`command -v sol2uml`). If absent: `npm i -g sol2uml`. 5. **Verify you are on the correct branch.** This is the most important prereq: every check diffs the on-chain deployment against the *local* code, so a wrong branch (or a dirty tree) silently produces meaningless results. Match the checkout to the PR head: - `gh pr view "$PR" --json headRefName,state,mergeCommit` - `git rev-parse --abbrev-ref HEAD` and `git status --porcelain` - Require the current branch to equal the PR's `headRefName`, OR — if the PR is already merged — that the checkout contains its merge commit (`git merge-base --is-ancestor HEAD`). - On mismatch or a dirty working tree → **STOP**. Tell the user to `gh pr checkout ` (or `git checkout ` / `git stash`) and re-run. Do not verify against the wrong code. ## Step 1 — Gather PR context 1. `PR=$1`. `gh pr view "$PR" --json title,body,files,baseRefName,headRefName,state,url`. 2. Find the changed Foundry deploy script(s): `git diff --name-only origin/master...HEAD -- 'contracts/scripts/deploy/**/*.s.sol'` (or read the PR `files`). Ignore `000_Example.s.sol`. The folder under `scripts/deploy//` gives the **network** (mainnet, base, sonic, hyperevm, …) — use it for `sol2uml --network ` and map it to the chain deployment file (`build/deployments-1.json`, `-8453.json`, `-146.json`, `-999.json`, etc.). 3. From each script's `_execute()`, enumerate every contract creation (`new (args)`) and every `_recordDeployment("", address(...), type().name)` call. Resolve `` → address from the matching entry in the chain deployment JSON's `contracts` array (`.name`, `.implementation`). The JSON field is named `implementation` even for proxies and non-implementation contracts. If a newly recorded key is absent from the committed JSON, mark checks 1/3/4 ❌. 4. The script identifier is the string passed to `AbstractDeployScript("")`. Resolve its `proposalId`, `tsDeployment`, and `tsGovernance` from the matching entry in the chain deployment JSON's `executions` array. Build expected governance actions from `_buildGovernanceProposal()` calls to `govProposal.action(target, "signature(types)", abi.encode(args))`. If `proposalId` is `0`/absent while governance is expected, mark check 5 ⚠️ ("no proposalId recorded — resolve from on-chain Governor events or ask the deployer"). 5. Working set: `[{key, contractType, address, constructorArgs, proposalId, network, deployScript}]`. ## Step 2 — Run the checks Checks 2/3/4 are per-address and independent — fan them out with parallel `Task` sub-agents (one per deployed contract) when there are several; otherwise run inline. Each check yields: status (✅ pass / ⚠️ needs-human / ❌ fail), one-line evidence, a details block, and a confidence (High/Med/Low). Absence of evidence is ⚠️/❌, never ✅. **1 — All deployed contracts listed in the PR description** - Compare the Step-1 deployed name+address set against the PR body. - ✅ every deployed contract (name and address) appears; ❌ a deployed contract is missing; ⚠️ names present but addresses missing/ambiguous. **2 — Verified on-chain code (and dependencies) matches the codebase** - For each deployed **implementation** address, from `contracts/`: `sol2uml diff
.,node_modules --network --apiKey "$ETHERSCAN_API_KEY"` This downloads the explorer-verified source for the address and diffs it (and its dependencies) against the local checkout. - ✅ sol2uml reports no differences (every file identical); ❌ any file differs — quote the differing files/lines. For `*Proxy` addresses run the same diff against the proxy source; expect the standard proxy to match. - Confidence High when the diff is clean/empty. **3 — Constructor arguments are correct** - Read the constructor expression (`new (args)`) corresponding to each `_recordDeployment` in `_execute()` and resolve constants/addresses used by it. - Compare those arguments to the on-chain "Constructor Arguments" on the explorer (Etherscan contract page, or decode the tail of the creation tx input). Foundry's chain deployment JSON does not store constructor args or transaction hashes, so do not treat it as evidence for this check. They must match positionally. - ✅ all args match; ❌ any positional mismatch (show Foundry-script value vs on-chain). Proxies typically have no constructor args — note `[]`. **4 — The initialize / interaction tx matches the deploy script** - If `_execute()` performs a post-deploy call — typically a proxy `initialize(...)`/`_initialize(...)` — locate the corresponding on-chain tx (explorer tx list for the proxy address, or the proxy's deployment receipt) and confirm the **arguments match the Foundry script**. - ✅ the initialize tx args match the script; ⚠️ the script has no init/interaction call (nothing to check); ❌ args differ (show the diff). **5 — Governance proposal matches the deploy script** - Read the on-chain proposal directly via ethers — do NOT use `pnpm ops proposal --id`, it parses the id as a float and overflows on real (77-digit) proposalIds. Use the GovernorSix (`addresses.mainnet.GovernorSix`) ABI with the id as a `BigNumber`: `g.state(id)` and `g.getActions(id) -> (targets, values, signatures, calldatas)` (calldatas are selector-stripped — the signature is a separate string). - Build expected actions from `_buildGovernanceProposal()`: resolve every target through constants or `resolver.resolve("")` and the chain deployment JSON, keep the signature passed to `govProposal.action`, and evaluate the corresponding `abi.encode` arguments to compare against each on-chain calldata. - ✅ identical (same count, targets, signatures, calldatas); ❌ any divergence (show it). Report the proposal `state`: `Executed` for a fully-executed deploy; `Pending`/`Active`/ `Queued` is normal when reviewing before execution — flag it but it is not a failure. **6 — Smoke tests after fork execution** — SKIPPED (per project decision). Mark N/A. ## Step 3 — Synthesize Verdict = **VERIFIED** only if checks 2,3,4,5 are ✅ (check 1 may be ⚠️ if the sole gap is documentation; check 4 may be ⚠️ if there is genuinely no init/interaction call). Any ❌ in 2–5, or an unresolved ⚠️, → **BLOCKERS FOUND**. Emit the report: ``` # Deployment PR Verification — # () Verified against: <branch>@<short-sha> Foundry script(s): <list> | Network: <net> | Proposal: <proposalId> Verdict: <VERIFIED | BLOCKERS FOUND> - [<✅|⚠️|❌>] 1. All deployed contracts listed in PR description — <evidence> (conf) - [<✅|⚠️|❌>] 2. Verified code (+deps) matches codebase (sol2uml diff) — <evidence> (conf) - [<✅|⚠️|❌>] 3. Constructor args correct — <evidence> (conf) - [<✅|⚠️|❌>] 4. Initialize/interaction tx matches deploy script — <evidence> (conf) - [<✅|⚠️|❌>] 5. Governance proposal matches deploy script — <evidence> (conf) - [⏭️] 6. Smoke tests — skipped (N/A) ## Details ### 2. sol2uml diff <per-address: address, clean/diff, differing files> ### 3. Constructor args <per-address: Foundry-script args vs on-chain args> ### 4. Initialize tx <tx hash, decoded args vs Foundry-script args> ### 5. Proposal diff <on-chain getActions vs script actions, or "identical"> ## Human still owes (manual) - Off-chain Safe/multisig follow-ups noted in the Foundry script. - That the PR's stated intent matches the on-chain effect (judgment). - Anything marked ⚠️ above. ``` ## Notes / common false positives - For `sol2uml diff`, run from `contracts/` and source `.env` first so `$ETHERSCAN_API_KEY` is set; use `--network` matching the deploy folder (base→`base`, sonic→`sonic`, etc.). - A **proxy address will not match an implementation's source** — diff a `*Proxy` against the proxy contract, not the impl. - A clean `sol2uml diff` (no file differences) is the pass signal for check 2; treat any reported file difference as ❌ pending human review, not a warning. - If `proposalId` is `0`/absent, do not fabricate one — mark check 5 ⚠️ and ask the deployer. - `deployments/<network>/*.json` remains a Talos compatibility registry, not the source of truth for Foundry deployment execution. Use `build/deployments-<chainId>.json` for Foundry-recorded names, addresses, and execution metadata. - If a helper command errors/rate-limits, retry once, then mark that check ⚠️ "tool error" with the stderr tail and continue the others. Never silently pass. ## Do NOT - Never send transactions, never re-run the deployment, never edit files. Explorer + read-only RPC only. - Never declare VERIFIED while any of checks 2–5 is ❌ or an unresolved ⚠️.