一键导入
review-proposal
Review a Compound governance proposal using the verification checklist
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Review a Compound governance proposal using the verification checklist
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | review-proposal |
| description | Review a Compound governance proposal using the verification checklist |
| disable-model-invocation | true |
| argument-hint | <proposal-id> |
Review Compound governance proposal $ARGUMENTS following this verification guide.
This guide outlines the verification steps to assess the security and correctness of a Compound governance proposal.
You MUST write the review to a markdown file: reviews/proposal-$ARGUMENTS.md
The review file should follow the output template in Section 14.
You are allowed and encouraged to modify the proposal decoder (packages/decoder/src/) during the review if needed to:
If you modify the decoder, note the changes in your review under a "Decoder Updates" section.
Use these severity levels when documenting findings:
| Severity | Criteria | Examples |
|---|---|---|
| CRITICAL | Proposal will fail, funds at risk, or enables governance attack | Wrong recipient address, insufficient balance, bridge paused |
| HIGH | Incorrect parameters with significant financial impact | Wrong supply cap (off by 10x), incorrect interest rate |
| MEDIUM | Minor parameter mismatches or suboptimal values | Small discrepancy from forum, non-standard gas limit |
| LOW | Cosmetic issues, non-standard patterns | Unusual ordering of actions, redundant approvals |
| INFO | Observations, no action required | First use of a new bridge route, large but correct transfer |
First, decode the proposal calldata to understand what actions it performs:
pnpm decode $ARGUMENTS
Identify:
CRITICAL STEP: Before detailed verification, fetch the forum discussion link from Tally and compare decoded values against the forum's "ground truth" values.
https://www.tally.xyz/gov/compound/proposal/$ARGUMENTSFor each parameter change in the proposal, verify against forum values:
| Parameter Type | What to Check |
|---|---|
| Supply Caps | Old → New values match forum exactly |
| Collateral Factors | Percentage changes match forum |
| Liquidation Factors | Percentage changes match forum |
| Interest Rate Curves | Base, Slope Low, Kink, Slope High match forum |
| Price Feeds | New addresses match forum (if specified) |
| Token Amounts | Transfer amounts match forum budget |
Create a comparison table in your review:
| Parameter | Forum Value | Decoded Value | Status |
|-----------|-------------|---------------|--------|
| USDC Supply Cap | 500,000 → 0 | 500.00K → 0 | ✓ MATCH |
| Borrow Kink | 90% → 85% | 85% (unchanged) | ⚠️ NOTE |
| Transfer Amount | 10,000 COMP | 9,500 COMP | **CRITICAL MISMATCH** |
CRITICAL: If decoded values don't match forum values:
Do not trust Etherscan labels alone - they are user-submitted and not authoritative proof.
| Contract Type | Where to Verify |
|---|---|
| Compound V2 contracts (Comptroller, Timelock, COMP) | compound-protocol/networks/mainnet.json |
| Compound III contracts (Comet, CometRewards, Configurator) | comet/deployments/{chain}/{market}/roots.json |
| OP Stack bridges (Base, Optimism) | Base Docs or Optimism Docs |
| Bridged tokens | On-chain: query REMOTE_TOKEN() or l1Token() to verify L1 counterpart |
Always verify relationships on-chain:
# Verify Timelock is admin of Comptroller
cast call <COMPTROLLER> "admin()(address)"
# Verify COMP token address from Comptroller
cast call <COMPTROLLER> "getCompAddress()(address)"
# Verify bridged token links to correct L1 token
cast call <L2_TOKEN> "REMOTE_TOKEN()(address)" --rpc-url <L2_RPC>
# Verify CometRewards is configured with correct reward token
cast call <COMET_REWARDS> "rewardConfig(address)" <COMET_ADDRESS> --rpc-url <L2_RPC>
# Verify Governor points to correct Timelock
cast call <GOVERNOR> "timelock()(address)"
Verify the proposal will not revert during execution.
# Check source has sufficient balance
cast call <TOKEN> "balanceOf(address)(uint256)" <SOURCE_ADDRESS>
# Check current allowances (should be 0 or sufficient)
cast call <TOKEN> "allowance(address,address)(uint256)" <OWNER> <SPENDER>
# Check if bridges are paused
cast call <L1_STANDARD_BRIDGE> "paused()(bool)"
cast call <OPTIMISM_PORTAL> "paused()(bool)"
# Check if bridge route has been used before (proves it works)
cast call <L1_STANDARD_BRIDGE> "deposits(address,address)(uint256)" <L1_TOKEN> <L2_TOKEN>
For _grantComp:
For approve:
For depositERC20To (bridge):
deposits mapping or previous transactions)_minGasLimit should be adequate (200,000 is standard for ERC20 deposits)Verify amounts are consistent across related actions:
| Parameter | Expected Value | Notes |
|---|---|---|
_minGasLimit | 200,000 | Optimism recommended |
_extraData | 0x | Usually empty |
_l1Token must match the token being approved_l2Token must be the correct bridged representation (verify with REMOTE_TOKEN())_to must be the intended recipient (e.g., CometRewards contract)# Verify Governor is active and legitimate
cast call <GOVERNOR> "proposalCount()(uint256)"
# Verify Governor's Timelock matches protocol Timelock
cast call <GOVERNOR> "timelock()(address)"
# Check quorum requirement
cast call 0x309a862bbC1A00e45506cB8A802D1ff10004c8C0 "quorumVotes()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
# Check proposal state and votes
cast call 0x309a862bbC1A00e45506cB8A802D1ff10004c8C0 "proposals(uint256)((uint256,address,uint256,uint256,uint256,uint256,uint256,uint256,bool,bool))" $ARGUMENTS --rpc-url https://ethereum-rpc.publicnode.com
For proposals that upgrade contract implementations (_setImplementation, upgradeTo, upgradeAndCall):
# Get current implementation
cast call <PROXY> "implementation()(address)" --rpc-url https://ethereum-rpc.publicnode.com
# Verify new implementation is verified on Etherscan
# Check: https://etherscan.io/address/<NEW_IMPL>#code
| Check | Status |
|---|---|
| New implementation is verified on Etherscan | |
| Storage layout is compatible (no slot collisions) | |
| Initializer cannot be called again (or is intentionally callable) | |
| New implementation has been audited | |
| Upgrade path has been tested |
If the proposal upgrades a proxy, compare storage layouts between old and new implementations to ensure no collisions.
Check the current state to understand the proposal's impact:
# For rewards top-ups: check current rewards balance
cast call <REWARD_TOKEN> "balanceOf(address)(uint256)" <REWARDS_CONTRACT> --rpc-url <L2_RPC>
| Condition | How to Check | Risk |
|---|---|---|
| Insufficient token balance | balanceOf() on source | Transaction reverts |
| Unauthorized caller | Verify execution path through governance | Transaction reverts |
| Bridge paused | paused() on bridge contracts | Transaction reverts |
| Token not bridgeable | Check deposits() mapping or history | Transaction reverts |
| Insufficient gas limit | Compare to recommended values | L2 finalization fails, funds stuck |
| Wrong token addresses | Verify with REMOTE_TOKEN() | Funds sent to wrong destination |
Analyze vectors that could prevent proposal execution between now and when it executes.
Important: Every finding must include its verification source (cast command, URL, or derivation).
Check if other pending proposals could drain the same tokens:
# Check states of recent proposals (0=Pending, 1=Active, 2=Canceled, 7=Executed)
for i in {518..525}; do
echo "Proposal $i: $(cast call 0x309a862bbC1A00e45506cB8A802D1ff10004c8C0 'state(uint256)(uint8)' $i --rpc-url https://ethereum-rpc.publicnode.com)"
done
# Decode pending proposals to check what tokens they use
pnpm decode <PROPOSAL_ID>
Document findings as:
| Proposal | State | Asset | Source |
|---|---|---|---|
| 522 | Pending | USDC | cast call 0x309a... 'state(uint256)(uint8)' 522 |
| 523 | Pending | COMP | pnpm decode 523 |
Calculate the margin between available balance and required amount:
# Check current balance
cast call <TOKEN> "balanceOf(address)(uint256)" <TIMELOCK> --rpc-url https://ethereum-rpc.publicnode.com
Document findings as:
| Metric | Value | Source |
|---|---|---|
| Available | 133,376 USDC | cast call 0xA0b8... "balanceOf(address)(uint256)" 0x6d90... |
| Required | 57,500 USDC | Decoded from proposal Action #0 |
| Buffer | 75,876 USDC (132%) | Calculated: Available - Required |
Calculate minimum time for a malicious proposal to execute:
# Get governance parameters
cast call <GOVERNOR> "votingDelay()(uint256)" # blocks until voting starts
cast call <GOVERNOR> "votingPeriod()(uint256)" # blocks for voting
cast call <TIMELOCK> "delay()(uint256)" # seconds in timelock queue
Document findings as:
| Phase | Value | Human Readable | Source |
|---|---|---|---|
| Voting Delay | 13140 blocks | 1.82 days | cast call 0x309a... "votingDelay()(uint256)" |
| Voting Period | 19710 blocks | 2.73 days | cast call 0x309a... "votingPeriod()(uint256)" |
| Timelock Delay | 172800 seconds | 2.00 days | cast call 0x6d90... "delay()(uint256)" |
| Total | - | ~6.5 days | Calculated: (13140+19710)*12s/86400 + 172800/86400 |
Check if Timelock has token allowances that could be exploited:
# Check allowances to known spenders (bridges, etc.)
cast call <TOKEN> "allowance(address,address)(uint256)" <TIMELOCK> <SPENDER> --rpc-url https://ethereum-rpc.publicnode.com
Document findings as:
| Spender | Allowance | Source |
|---|---|---|
| Base L1StandardBridge | 0 | cast call 0xA0b8... "allowance(address,address)(uint256)" 0x6d90... 0x3154... |
For proposals calling initialize() or similar one-time functions, verify access control:
# Check who can call the function (read source code on Etherscan)
# Example: Streamer.initialize() has onlyStreamCreator modifier
# Verify the authorized caller
cast call <CONTRACT> "streamCreator()(address)" --rpc-url https://ethereum-rpc.publicnode.com
# Check current state
cast call <CONTRACT> "startTimestamp()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
Document findings as:
| Check | Value | Source |
|---|---|---|
| Access Control | onlyStreamCreator modifier | Etherscan source |
| Authorized Caller | Timelock (0x6d90...) | cast call <CONTRACT> "streamCreator()(address)" |
| Current State | Not initialized (startTimestamp=0) | cast call <CONTRACT> "startTimestamp()(uint256)" |
For proposals involving centralized stablecoins (USDC, USDT):
# Check if token is paused
cast call <TOKEN> "paused()(bool)" --rpc-url https://ethereum-rpc.publicnode.com
# Check if addresses are blacklisted
cast call <TOKEN> "isBlacklisted(address)(bool)" <ADDRESS> --rpc-url https://ethereum-rpc.publicnode.com
Document findings as:
| Check | Status | Source |
|---|---|---|
| USDC Paused | false | cast call 0xA0b8... "paused()(bool)" |
| Timelock Blacklisted | false | cast call 0xA0b8... "isBlacklisted(address)(bool)" 0x6d90... |
| Vector | Risk Level | Mitigation | Verification |
|---|---|---|---|
| Competing proposals | Low/Medium/High | Description | pnpm decode + cast call state() |
| Governance attack | Low | X-day detection window | Governance parameter queries |
| Allowance exploitation | None/Low | Current allowances | cast call allowance() |
| Front-running | None/Low | Access control details | Etherscan source + cast call |
| Token pause/blacklist | External | Current status | cast call paused() / isBlacklisted() |
| Balance race | Low/Medium | X% buffer | cast call balanceOf() |
Simulate the proposal execution on a Tenderly virtual testnet to verify it executes successfully end-to-end.
packages/simulator/proposal.config.json with your Tenderly RPC URL:{
"chains": {
"mainnet": {
"rpcUrl": "https://virtual.mainnet.eu.rpc.tenderly.co/<YOUR-TESTNET-ID>",
"chainId": "1",
"timelockAddress": "0x6d903f6003cca6255D85CcA4D3B5E5146dC33925",
"governorAddress": "0x309a862bbC1A00e45506cB8A802D1ff10004c8C0"
}
}
}
# From the monorepo root, simulate a proposal
pnpm simulate $ARGUMENTS
# Simulate with persistence (creates snapshot)
pnpm simulate $ARGUMENTS --persist
# Simulate each action separately (useful for debugging)
pnpm simulate $ARGUMENTS --separately
Delegating 300000000000000000000000 COMP to 0x73AF3bcf944a6559933396c1577B257e2054D935...
Delegate transaction sent: 0x...
Syncing state...
Proposal created with ID: 522
Current block number: 24282349
Advancing to 24284235 block to make proposal active...
Casting vote for proposal...
Queueing proposal...
Advancing time by 1209600 seconds...
Executing proposal...
Execution result: 0x...
| Output | Meaning |
|---|---|
Execution result: 0x... | Proposal executed successfully |
Skipping target: 0x... | Target is not a bridge (mainnet-only action) |
| Error/revert message | Proposal would fail - investigate the cause |
Document findings as:
| Check | Result | Source |
|---|---|---|
| Simulation Status | Success | pnpm simulate $ARGUMENTS |
| Execution TX | 0xdc77... | Tenderly virtual testnet |
| L2 Bridging | N/A (mainnet-only) | Skipped targets in output |
If the proposal includes a test report, verify:
Watch out for these known issues:
| Pitfall | Description | How to Detect |
|---|---|---|
| Unichain mislabeling | Bridge at 0x81014F44... shows as "Optimism" in some decoders | Verify via Etherscan |
| "Unchanged" values | Decoder shows "unchanged" when a prior proposal already applied the change | Check recent executed proposals |
| Decimal confusion | USDC/USDT have 6 decimals, COMP/WETH have 18 | Verify token decimals on-chain |
| Stale forum posts | Forum post may have been updated after initial posting | Check forum post edit history |
| Multiple recipients | Bridge proposals may split funds across multiple L2 contracts | Verify all recipients are correct |
| Gas limit too low | Non-standard _minGasLimit for complex L2 operations | Compare to similar past proposals |
| The required gas exceed tx gas limit | The tx gas limit on Ethereum is 16 mil. No proposal should exceed this. | Check the current tx limit and compare it to the one consumed by the proposal. |
Write your review to reviews/proposal-$ARGUMENTS.md using this template:
# Proposal $ARGUMENTS Review
**Date:** YYYY-MM-DD
**Reviewer:** [Name]
**Status:** PENDING REVIEW | APPROVED | APPROVED WITH NOTES | REJECTED
## Summary
- **Proposal Type:** [Parameter Change / Token Transfer / Bridge / Upgrade / Mixed]
- **Risk Level:** [Low / Medium / High / Critical]
- **Actions:** [Number] actions across [Number] chains
[1-2 sentence description of what this proposal does]
## Findings
| # | Severity | Description | Status |
|---|----------|-------------|--------|
| 1 | INFO | Example finding | Verified |
## Forum Comparison
| Parameter | Forum Value | Decoded Value | Status |
|-----------|-------------|---------------|--------|
| ... | ... | ... | ✓ MATCH |
## Address Verification
| Address | Expected | Verified | Source |
|---------|----------|----------|--------|
| ... | ... | ✓ | [Source] |
## Execution Feasibility
| Check | Result | Source |
|-------|--------|--------|
| Token Balance | Sufficient | `cast call ...` |
| Bridge Status | Not Paused | `cast call ...` |
## DoS Risk Analysis
| Vector | Risk Level | Notes |
|--------|------------|-------|
| Competing proposals | Low | No conflicting proposals |
## Simulation Results
| Check | Result |
|-------|--------|
| Mainnet Simulation | Success/Failed |
| L2 Relay | N/A or Success/Failed |
## Decoder Updates
[If you modified the decoder during this review, document changes here]
- None
## Recommendation
**[APPROVE / APPROVE WITH NOTES / REJECT]**
[Explanation of recommendation]
# Ethereum
--rpc-url https://ethereum-rpc.publicnode.com
# Base
--rpc-url https://base-rpc.publicnode.com
# Optimism
--rpc-url https://optimism-rpc.publicnode.com
# Arbitrum
--rpc-url https://arbitrum-one-rpc.publicnode.com
# Polygon
--rpc-url https://polygon-bor-rpc.publicnode.com
# Ronin
--rpc-url https://api.roninchain.com/rpc
# Unichain
--rpc-url https://mainnet.unichain.org
0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B0x6d903f6003cca6255D85CcA4D3B5E5146dC339250xc00e94Cb662C3520282E6f5717214004A7f268880x309a862bbC1A00e45506cB8A802D1ff10004c8C00x3154Cf16ccdb4C6d922629664174b904d80F2C350x49048044D57e1C92A77f79988d21Fa8fAF74E97e0x99C9fc46f92E8a1c0deC1b1747d010903E884bE10xbEb5Fc579115071764c7423A4f12eDde41f106Ed0x81014F44b0a345033bB2b3B21C7a1A308B35fEeAWARNING: The Unichain bridge address (0x81014F44...) is often mislabeled by decoders as "Optimism". Always verify the bridge network via Etherscan labels.