Beanstalk's governance measured voting power from a wallet's current token balance, and offered an emergencyCommit path that executes a proposal 24 hours after nomination if it holds a two-thirds supermajority. The attacker submitted BIP-18 (transfer everything to me) and BIP-19 (a $250k donation to Ukraine, as cover), waited out the 24 hours, then flash-loaned roughly $1 billion from Aave, Uniswap and SushiSwap, converted it into Beanstalk LP tokens, voted with it, committed, repaid the loan and left — all inside a single block.
function emergencyCommit(uint32 bip) external { require(isNominated(bip), "Governance: Not nominated."); require(block.timestamp >= timestamp(bip).add(C.getGovernanceEmergencyPeriod()), "Governance: Too early."); require(isActive(bip), "Governance: Ended."); require(canPropose(msg.sender), "Governance: Not enough Stalk."); // ❌ voting power read from the CURRENT balance, this block require(bipVotePercent(bip).greaterThanOrEqualTo(C.getSuperMajority()), "Governance: Must have supermajority."); _execute(msg.sender, bip, false, true); } // ✅ the fix is one word: snapshot. // votes = balanceOfAt(voter, proposal.snapshotBlock) // A flash loan cannot rewrite a block that already happened.
balanceOf() at execution time, assume an attacker holds an unlimited amount of it for one transaction.Entries in the SAFE database that describe this failure. The first ones name this incident directly.
Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.
Governance is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.