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:

  1. Flash-borrow the governance token (from any liquidity venue).
  2. Propose a malicious treasury-draining proposal.
  3. Cast the vote — your weight is the entire flash-loaned amount.
  4. Wait one block. Execute the proposal. Drain the treasury.
  5. Repay the flash loan. Net cost: flash-loan fee.
  6. Findings

    IDSevTitle
    FLASH-001CriticalVote weight read from spot balance
    FLASH-002CriticalVoting window is one second; no delay
    GOV-001Highexecute() 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