Rugproof Security Audit — FlashLoanGovernance.sol
2026-05-13 · grade F · 2 Critical, 1 High
Summary
Governance contract whose vote weight is read from the spot ERC-20 balanceOf(msg.sender) at the moment of vote-casting, with a one-second voting window after proposal creation. This is structurally identical to the Beanstalk hack of April 2022 ($182M loss).
Single-block exploit:
- Flash-borrow the governance token (from any liquidity venue).
- Propose a malicious treasury-draining proposal.
- Cast the vote — your weight is the entire flash-loaned amount.
- Wait one block. Execute the proposal. Drain the treasury.
- Repay the flash loan. Net cost: flash-loan fee.
ERC20Votes+ERC20Permit(checkpointed voting power)GovernorVotesQuorumFraction(4)(4% of supply quorum)GovernorTimelockControlwithTimelockController(2 days, ..., ...)votingDelay() = 1 days,votingPeriod() = 3 days
Findings
| ID | Sev | Title |
|---|---|---|
| FLASH-001 | Critical | Vote weight read from spot balance |
| FLASH-002 | Critical | Voting window is one second; no delay |
| GOV-001 | High | execute() callable immediately at deadline (no timelock) |
FLASH-001 — Spot balance vote weight (Critical)
uint256 weight = govToken.balanceOf(msg.sender); // ← spot
Use ERC20Votes checkpoints + snapshot at proposal creation.
FLASH-002 — One-second voting window (Critical)
deadline: block.timestamp + 1
Set a real voting delay (industry: 1 day) AND voting period (≥3 days). The delay is the actual flash-loan defense — checkpoints alone are insufficient if the snapshot block is the same as the borrow block.
GOV-001 — No timelock between approval and execution (High)
After voting passes, execute() can be called immediately. Even a 24h timelock would let observers raise alarm and emergency pause. Use OZ TimelockController between Governor and the executable target.
Recommendation
Switch to OpenZeppelin's Governor with:
Skill detail: flash-loan-attacks, governance-specialist.
Generated by Rugproof — https://omermaksutii.github.io/RugProof