Beanstalk Governance (2022)
Smart contract security audit by TierZero · 2023-04-15
Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.
Findings summary
Independent research — not a commissioned audit.
What the protocol did
Beanstalk was a credit-based stablecoin protocol on Ethereum. Its token, BEAN, targeted a $1 peg not through overcollateralized vaults but through an algorithmic incentive system: users deposited whitelisted assets (BEAN, BEAN:ETH and BEAN:3CRV LP tokens, and others) into a shared pool called the Silo and received Stalk and Seeds in return. Stalk was Beanstalk's governance weight — it grew the longer a deposit sat in the Silo — and it doubled as voting power over Beanstalk Improvement Proposals (BIPs), the mechanism used to change protocol parameters, whitelist assets, or move treasury funds.
Governance was on-chain and self-executing. A BIP needed a supermajority of outstanding Stalk to pass, and the contract exposed an emergencyCommit path that let a proposal execute the moment it crossed a two-thirds threshold, rather than waiting out the normal voting window. That design decision — voting weight that can be acquired instantly, wired directly to an execution path with no delay — is the entire postmortem in one sentence.
The vulnerability
The bug class is flash-loan-financed governance takeover combined with a timelock-free execution path. Two properties have to both be true for this to be catastrophic:
- Voting power is a function of a spot token balance (or a balance with no vesting/lock beyond a single block), so it can be rented for the duration of one transaction.
- The contract allows a proposal to execute as soon as a quorum threshold is met, without a mandatory delay between "vote passes" and "vote executes."
Illustrative pattern (simplified from Beanstalk's actual flow):
function emergencyCommit(uint256 bipId) external {
uint256 votes = getStalkVotedFor(bipId);
uint256 totalStalk = totalStalkSupply();
require(votes * 3 >= totalStalk * 2, "quorum not met"); // 2/3 supermajority
_execute(bipId); // executes immediately, same transaction
}
Nothing here checks when the voting Stalk was acquired or requires it to have existed before the proposal was created. A snapshot-based design (voting power fixed at proposal-creation block) would have neutralized this; a same-transaction balance check does not. This is the same root cause we flag whenever governance weight, staking rewards, or reward-distribution logic reads balanceOf() or an equivalent live balance instead of a checkpointed one — it's structurally identical to flash-loan price-oracle manipulation, just applied to a voting oracle instead of a price oracle.
How the exploit worked
- The attacker used flash loans from Aave, Uniswap, and SushiSwap to borrow roughly $1 billion in stablecoins (DAI, USDC, USDT) in a single transaction.
- That capital was swapped into the Silo's whitelisted deposit assets and deposited into Beanstalk, instantly minting a supermajority of Stalk — the attacker briefly controlled roughly two-thirds of all governance weight.
- In the same transaction, the attacker submitted a BIP that had been pre-staged to look like a charitable donation proposal (funding Ukraine relief) but contained an additional rider transferring Silo-held collateral to a contract the attacker controlled.
emergencyCommitfired immediately because quorum was met, executing the malicious payload with no timelock and no human review window.- The attacker's contract swept roughly $182 million in deposited assets, swapped them back through the same DEX pools, repaid the flash loans, and kept the remainder — net profit was in the neighborhood of $80 million, with a small fraction (roughly $250K) routed to an actual Ukraine donation address, apparently to complicate the narrative.
- The Silo withdrew liquidity, and the entire sequence completed atomically in one block; there was no intervention window because none existed by design.
How an audit catches this
This is a governance-model review, and it's one of the first things we check on any protocol where voting weight derives from a token balance. Concretely:
- Trace every path that reads voting power and confirm it uses a checkpoint/snapshot (block-number- or timestamp-indexed) rather than a live
balanceOf()-style read. If it doesn't, that's a finding regardless of whether anemergencyCommit-style fast path exists. - Enumerate every execution path from "proposal passes" to "proposal executes" and require a minimum timelock on all of them, including any "emergency" or fast-track branch — fast paths are exactly where this bug hides because they're added later and reviewed less.
- Model the cost of acquiring supermajority voting weight via flash loan against the value the DAO/treasury controls; if borrowing cost is near zero and the payoff exceeds it, the governance design is unsound independent of any other bug.
- Write an invariant test that mints flash-loan-scale capital, deposits it, and attempts to pass and execute a proposal within one transaction — this is the kind of scenario we build into the test suite during a full engagement, alongside the fuzzing and invariant work we describe in our write-up on formal verification and property-based testing for trading program security.
- Review any allow/approve-style delegation the same way — the underlying failure mode (unlimited authority granted on a live, spoofable balance) is the same one we cover when discussing approval-based attack surface in EVM contracts.
Remediation
The fix is structural, not a one-line patch:
- Snapshot voting power at proposal creation (Compound-style checkpoints), so capital acquired after a proposal exists cannot vote on it.
- Enforce a mandatory timelock between quorum being reached and execution, with no bypass — if an emergency path is required for genuine crises, gate it behind a separate, slower-moving multisig or council rather than raw token-weighted voting.
- Cap how much of total voting supply a single transaction can newly mint or acquire, or require voting tokens to be locked for a minimum duration before they count toward quorum.
- Add circuit breakers on Silo withdrawals so a single-block, single-transaction full drain isn't mechanically possible even if governance is compromised.
Governance logic gets far less scrutiny than pricing and accounting code, and that's exactly why it's a recurring source of nine-figure losses. If your protocol has any on-chain governance path — even one gated behind "emergency" naming — it belongs in scope for a full smart contract audit, not a quick read-through.
Need your contracts audited?
Manual review + tooling across TON, Solana and EVM — certificate and full report included.
Get an audit