dao
governance
attack

Governance Attack Vectors: How Attackers Drained $182M From Beanstalk in 24 Hours

$182 million. Gone in a single governance vote. The Beanstalk exploit didn't require a zero-day or a complex flash loan sandwich — the attacker just borrowed enough tokens to pass a malicious proposal. If you're building or investing in a DAO, this is the threat model you're probably not thinking about.

Updated October 4, 2026
Duron Epps, Founder
14 min read
Share:X / TwitterLinkedIn

$182 million. Gone in a single governance vote. The Beanstalk exploit in April 2022 didn't require a zero-day vulnerability or months of preparation — the attacker borrowed enough voting power via flash loan, passed a malicious proposal they'd quietly submitted 24 hours earlier, and drained the entire treasury. One transaction. Done. The protocol never recovered.

Governance attacks are the most underrated exploit category in DeFi right now. Everyone's scanning for reentrancy. Everyone's checking for integer overflows. Nobody's stress-testing what happens when a well-funded attacker owns 51% of your vote for 13 seconds.

Here's What Actually Happened at Beanstalk

The attacker submitted a legitimate-looking governance proposal (BIP-18) one day before the attack. Beanstalk's governance allowed any proposal to be executed immediately once it hit a supermajority threshold. No timelock. No delay between "proposal passes" and "proposal executes."

On attack day, the attacker took a $1B flash loan across Aave, Uniswap, and SushiSwap — enough to acquire ~67% of Beanstalk's Stalk voting tokens in a single block. They voted yes on BIP-18 with their flash-borrowed voting power. The proposal passed the supermajority threshold. The proposal executed immediately. It drained all protocol assets to the attacker's wallet. The flash loan was repaid. The entire sequence completed in one transaction.

The stolen funds went to Ukraine's official crypto donation address (the attacker's stated reason) and to themselves. Whether you find that noble or not is irrelevant — the point is the protocol is dead and $182M moved because of a missing timelock and snapshot-based voting that didn't account for flash loan dynamics.

The Four Governance Attack Vectors You Need to Know

1. Flash Loan Voting Power

This killed Beanstalk. If your governance token's voting power is measured at the moment of the vote rather than at a prior snapshot block, an attacker can borrow massive supply, vote, and return the tokens — all in one transaction. They never actually "own" your governance token. They rent it for 13 seconds.

The fix is using historical snapshots for voting power. ERC20Votes in OpenZeppelin 5.x does this correctly with getPastVotes(address, blockNumber). If your protocol isn't using past-block snapshots, you're vulnerable.

2. Proposal Execution Without Timelock

Beanstalk had this too — the double failure that made the exploit possible. Even with good snapshot logic, if a proposal can execute the moment it passes, an attacker who acquires legitimate long-term voting power (not just flash loans) can push through malicious proposals before the community notices.

Compound, Uniswap, and most serious protocols enforce a 48-72 hour timelock between proposal passage and execution. This is your emergency brake. Without it, governance is a loaded gun on the table.

3. Low Quorum + Voter Apathy

This one's slower but just as deadly. If your quorum is set at 4% of circulating supply and 90% of your token holders are passive, a determined attacker only needs to accumulate 4% of supply on the open market — no flash loan required. They vote yes on a proposal that redirects treasury emissions to their wallet. Proposal passes. Nobody noticed because turnout was 4.2% and it met quorum.

I've seen protocols with $50M treasuries where governance could be captured for under $800K in token purchases. That's not a bug in the code — that's a bug in the tokenomics design.

4. Proposal Spam and Griefing

Less catastrophic, but worth knowing: attackers can submit dozens of malicious proposals simultaneously, overwhelming governance attention. While the community focuses on voting down the obvious bad proposals, the subtle one — the one that quietly adds an attacker-controlled address to a privileged role — slips through. This is social engineering at the protocol layer.

The Vulnerable Code Pattern

Here's what dangerous on-chain voting looks like in Solidity. This is the pattern that got Beanstalk killed (simplified for clarity):

// VULNERABLE: Solidity 0.8.24
// Uses current balance for voting power — flash loan attackable

contract VulnerableGovernor {
    IERC20 public governanceToken;
    mapping(uint256 => Proposal) public proposals;
    
    struct Proposal {
        address target;
        bytes callData;
        uint256 votesFor;
        uint256 votesAgainst;
        bool executed;
    }

    function vote(uint256 proposalId, bool support) external {
        // CRITICAL FLAW: reads CURRENT balance
        // Attacker flash-loans tokens, calls this, repays loan
        uint256 weight = governanceToken.balanceOf(msg.sender);
        
        if (support) {
            proposals[proposalId].votesFor += weight;
        } else {
            proposals[proposalId].votesAgainst += weight;
        }
    }

    function execute(uint256 proposalId) external {
        Proposal storage p = proposals[proposalId];
        uint256 totalSupply = governanceToken.totalSupply();
        
        // CRITICAL FLAW: no timelock — executes immediately on passage
        require(p.votesFor * 100 / totalSupply > 50, "No majority");
        require(!p.executed, "Already executed");
        
        p.executed = true;
        // Executes arbitrary calldata — could drain treasury
        (bool success,) = p.target.call(p.callData);
        require(success, "Execution failed");
    }
}

Two fatal flaws stacked on top of each other. Now here's how it should look:

// SECURE: Solidity 0.8.24 with OpenZeppelin 5.x
// Uses ERC20Votes snapshot + TimelockController

import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";

contract SecureGovernor is 
    Governor, 
    GovernorVotes,           // Uses getPastVotes() — snapshot-based
    GovernorTimelockControl, // Mandatory delay before execution
    GovernorVotesQuorumFraction 
{
    constructor(
        IVotes _token,
        TimelockController _timelock
    )
        Governor("SecureDAO")
        GovernorVotes(_token)
        GovernorTimelockControl(_timelock)
        GovernorVotesQuorumFraction(10) // 10% quorum — adjust per your tokenomics
    {}

    // Voting delay: 1 day before voting opens after proposal
    // Forces proposal review period — no same-block shenanigans
    function votingDelay() public pure override returns (uint256) {
        return 7200; // ~1 day in blocks at 12s/block
    }

    // Voting period: 5 days
    function votingPeriod() public pure override returns (uint256) {
        return 36000; // ~5 days
    }

    // Timelock delay handles execution delay — set to 48h minimum in TimelockController
    // GovernorTimelockControl.executionDelay() reads from the TimelockController
    
    // getVotes() is overridden by GovernorVotes to call:
    // token.getPastVotes(account, block.number - 1)
    // This is the key — past snapshot, not current balance
    // Flash loans cannot manipulate a past block's state
}

// In your deployment script, set TimelockController minDelay to at least:
uint256 constant MIN_TIMELOCK_DELAY = 2 days;

The OpenZeppelin Governor suite gets this right out of the box — but only if you actually use it correctly. I've seen protocols fork Governor, rip out the timelock to "move faster," and ship a Beanstalk-in-waiting.

How I'd Catch This Before It Ships

When I'm auditing a governance contract, the first thing I look for isn't the obvious stuff. Here's my actual sequence:

Step 1: Find the voting power calculation. Ctrl+F for balanceOf anywhere near voting logic. If I see current-balance voting weight, that's an immediate critical finding. Should be getPastVotes or equivalent snapshot function.

Step 2: Map the proposal lifecycle. Proposal created → voting open → voting closes → execution. At every transition, ask: is there a time buffer? What's the minimum time between "proposal submitted" and "proposal executes"? Anything under 24 hours for a serious protocol is dangerous. Anything that can execute in the same block as the passing vote is catastrophic.

Step 3: Check who can bypass governance. This is where I find the real problems. Look for admin roles, owner functions, upgrade proxies — anything that can change protocol state without going through the voting process. I've seen "secure" governance systems with a multisig emergencyWithdraw() function that bypasses everything. That's not a governance system, that's governance theater.

Step 4: Model the attack economics. What percentage of circulating supply is the quorum? What does that cost at current prices? Is that cost lower than the treasury value? If it costs $500K to pass a governance vote and the treasury holds $10M, the protocol is economically exploitable. Static analysis tools don't catch this — it requires thinking about incentive structure.

Step 5: Look for proposal content validation. Can anyone submit a proposal? Is there a minimum token threshold to propose? Some protocols have strong voting security but zero proposal guards, letting attackers spam proposals or submit proposals that self-delegate treasury access in ways that look benign on first read.

The Counterintuitive Take

Everyone focuses on the voting mechanism. The real governance risk in 2024 is the upgrade proxy sitting behind your "immutable" governance system.

I've audited protocols where the governance contract itself was a UUPS proxy with an upgradeTo() function controlled by a 2-of-3 multisig that three founders held. The on-chain voting looked great — timelocks, snapshots, quorum, the works. But any two founders could upgrade the governance contract to a version that ignores all votes and just executes whatever they want.

Your governance security is only as strong as the least-protected admin key in your system. Map every privileged role before you audit the voting logic.

Static Tools Catch Half of This

Slither will flag balanceOf in voting context and some access control issues. That's useful. But Slither can't tell you that your 4% quorum is economically exploitable given your token's current liquidity, or that your proposal description is misleading enough to pass community review while hiding malicious calldata.

That's where AI-assisted analysis matters — catching the economic logic and the contract interaction patterns that pure static analysis misses. Run your governance contract through SmartContractAuditor.ai before you deploy. It flags the snapshot/timelock patterns specifically, explains why they're dangerous in plain English, and gives you actionable fixes — not just a list of line numbers.

Before You Ship or Ape In — Do These Five Things

  1. Check the voting weight source. Search the contract for balanceOf near any vote-counting logic. If it's reading current balance instead of a past snapshot, walk away or fix it before deploying.
  2. Find the timelock. Look for a TimelockController or equivalent delay mechanism between proposal passage and execution. If there isn't one, the protocol can be drained in a single block by anyone with enough flash-loan capital.
  3. Calculate the economic attack cost. Multiply quorum percentage by current token market cap. If that number is less than 20% of treasury value, the protocol is economically attackable. This is back-of-napkin math that saves you from disaster.
  4. Check for upgrade keys. Run grep -r "onlyOwner\|upgradeTo\|_setImplementation" ./contracts on any governance repo. Every privileged function that bypasses the vote is a potential governance backdoor.
  5. Paste the contract into SmartContractAuditor.ai before you deploy — or before you invest. It takes 30 seconds, flags flash loan voting vulnerabilities and missing timelocks specifically, and explains what's wrong in language that doesn't require a security background to understand.

Governance is the final boss of smart contract security because it controls everything else. Get it wrong and it doesn't matter how air-tight your AMM math is. The attacker just votes your treasury into their wallet.

Run your governance contract through SmartContractAuditor.ai before you deploy. Not after the proposal passes. Not after the attack. Now.

Further Reading

Published on
July 6, 2026