Beanstalk flash-loan governance takeover drains treasury assets
Exploit Transactions
0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7Victim Addresses
0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5EthereumLoss Breakdown
Similar Incidents
Audius Governance Reinitialization and Treasury AUDIO Drain
40%BUILD Governance Takeover and Unlimited Mint ACT Exploit
40%NFTX Doodles collateral accounting flaw enables flash-loan ETH extraction
33%DeRace vesting proxy ownership takeover and emergency exit
32%SilicaPools decimal-manipulation bug drains WBTC flashloan collateral
30%StakingRewards withdraw underflow drains all staked Uniswap V2 LP
30%Root Cause Analysis
Beanstalk flash-loan governance takeover drains treasury assets
1. Incident Overview TL;DR
An unprivileged adversary used a flash-loan-amplified governance attack against the Beanstalk protocol’s diamond contract (0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5) on Ethereum mainnet. By temporarily amassing a large Bean and Bean-ETH LP position and depositing it into the Beanstalk Silo, the attacker minted an overwhelming amount of governance roots in a single transaction. Because Beanstalk governance counted live Silo roots without a proposal-time snapshot or time-weighting, the attacker could immediately vote for their own malicious proposal (BIP‑18) and call emergencyCommit(18) in the same transaction. The BIP‑18 diamond cut then executed an init delegatecall that transferred the Beanstalk treasury’s entire Bean and Bean‑ETH Uniswap V2 LP balances to an attacker-controlled helper contract. After unwinding flash loans and swaps, the attacker’s EOA realized a large net profit in ETH terms while the protocol’s Bean and LP treasury was effectively drained.
2. Key Background
Beanstalk is implemented as a diamond proxy whose facets share a common AppStorage layout, with governance and Silo state stored in dedicated structs. The Silo tracks per-account Bean and Bean‑ETH LP deposits, deriving two key quantities: stalk (a measure of yield-bearing stake) and roots (the unit of governance voting power). Governance BIPs are created and managed via GovernanceFacet functions dispatched through the diamond’s fallback.
When a user deposits Bean or Bean‑ETH LP into the Silo via SiloFacet::depositBeans or SiloFacet::depositLP, the call flows through helper contracts (BeanSilo, LibSilo) that compute how much stalk and roots to mint. Importantly, LibSilo immediately updates:
- the depositor’s stalk and roots balances; and
- any in-progress BIP roots tallies via
incrementBipRootsinAppStorage.Governance.
There is no snapshot of voting power at proposal time and no requirement that Silo deposits remain in place beyond the current transaction. Governance thresholds (quorum and supermajority) are evaluated against the current roots recorded in storage whenever GovernanceFacet::vote or GovernanceFacet::emergencyCommit is called.
Beanstalk’s treasury reserves (including Bean and Bean‑ETH LP) are held directly in the diamond contract. Governance BIPs can modify the diamond by installing new facets and running an init delegatecall. In BIP‑18, the attacker deployed a malicious init contract that, when executed, read the Beanstalk treasury’s Bean and Bean‑ETH LP balances and transferred them to an attacker-controlled helper contract.
All of the above components—Silo deposits, governance calls, flash loans, AMM and Curve interactions—are publicly accessible on Ethereum mainnet. Any EOA can call the same interfaces under standard gas limits, and the attacker’s strategy does not rely on privileged keys or whitelisted roles, making this an ACT (anyone-can-take) opportunity.
3. Vulnerability Analysis & Root Cause Summary
The core vulnerability is a governance design flaw: Beanstalk governance derives voting power from live Silo roots without a proposal-time snapshot, time-weighting, or requirement for economically committed stake. Silo deposits can be created and removed within a single transaction, yet their associated roots are immediately counted toward BIP thresholds.
In the exploit, the attacker used flash loans and public DeFi routes to temporarily acquire a large Bean and Bean‑ETH LP position, deposited these assets into the Silo via SiloFacet, and thereby minted an overwhelming amount of roots for their helper contract. LibSilo::incrementBalanceOfStalk and incrementBipRoots immediately updated both the account’s roots and BIP‑18’s roots tally in governance storage. The attacker then invoked GovernanceFacet::vote and GovernanceFacet::emergencyCommit(18) in the same transaction, causing the diamond to execute a malicious BIP‑18 init that drained the Bean and Bean‑ETH LP treasury to an attacker-controlled address. After unwinding the flash loans, the adversary’s EOA realized a large ETH-denominated profit.
The invariant that governance power should reflect durable, economically committed stake was thus violated: transient, flash‑loan‑boosted deposits were sufficient to control Beanstalk’s treasury in a single block.
4. Detailed Root Cause Analysis
Governance and Silo invariant
The relevant governance safety invariant can be stated as:
O(σ_B, σ') = 1 if and only if Beanstalk governance decisions
(including emergencyCommit of BIPs) cannot be passed and executed
solely by same-transaction flash-loan-boosted Silo deposits that
are unwound immediately after execution; governance power must be
tied to economically committed stake measured in a time-consistent way.
In the observed exploit, this invariant is broken. The system permits an account to:
- Borrow capital via flash loans;
- Convert that capital into Bean and Bean‑ETH LP;
- Deposit those assets into the Silo;
- Mint a very large number of roots and have them counted toward BIP‑18’s roots tally; and
- Immediately execute and unwind all positions in the same transaction, leaving no lasting stake but permanently changing treasury ownership.
Code-level breakpoint
The concrete breakpoint lies in the Silo implementation and its interaction with governance. From the collected Beanstalk source (verified clone under:
artifacts/root_cause/data_collector/iter_2/contract/1/0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5/source
and
artifacts/root_cause/data_collector/iter_2/contract/1/0x448d330affa0ad31264c2e6a7b5d2bf579608065/source),
the relevant flow is:
// Simplified from SiloFacet/BeanSilo/LibSilo and AppStorage.Governance
function depositBeans(uint256 amount) external {
// ... transfer Bean into the diamond ...
LibSilo.deposit(msg.sender, amount, BEAN_TOKEN);
}
library LibSilo {
function deposit(address account, uint256 amount, address token) internal {
// compute stalk and roots for this deposit
// update per-account balances
incrementBalanceOfStalk(account, stalkDelta);
incrementBalanceOfRoots(account, rootsDelta);
// if there is an active BIP, update its roots as well
incrementBipRoots(account, rootsDelta);
}
}
incrementBipRoots writes into AppStorage.Governance and immediately increases the recorded roots for any in-progress proposal that the depositor has voted on. There is no check that the deposit persists beyond the current transaction, and governance logic later reads these updated roots when evaluating thresholds.
On the governance side, the collected GovernanceFacet sources show that:
proposecreates a BIP, sets its parameters, and records an initial vote and roots for the proposer based on current Silo roots; andvoteandemergencyCommitcheck quorum and supermajority thresholds using the current roots stored inAppStorage.Governance.
There is no owner/admin gating or privileged whitelist for these functions: any EOA with sufficient roots can call them via the diamond fallback.
Exploit pre-conditioning and setup
The attacker first established a baseline Silo position to participate in governance:
- Transaction
0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d(block14595189) is anadversary-crafteddeposit from EOA0x1c5dcdd006ea78a7e4783f9e6021c32935a10fb4into the Beanstalk Silo.
The trace (artifacts/root_cause/data_collector/iter_2/tx/1/0xf5a69898.../trace.cast.log) shows calls intoSiloFacetandLibSilo, establishing initial stalk and roots for0x1c5d....
Next, the attacker created and configured BIP‑18:
- Transaction
0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f(block14595906) is aGovernanceFacet::proposecall from0x1c5d...routed via the diamond’sfallback.
The associated metadata and receipt (artifacts/root_cause/seed/1/0x68cdec0a.../metadata.json,.../balance_diff.json,.../trace.cast.log, andartifacts/root_cause/data_collector/iter_1/tx/1/0x68cdec0a.../receipt.json) show the creation of BIP‑18, initial parameterization, and recording of an initial vote and roots for0x1c5d...inAppStorage.Governance.
By block 14602790 (pre‑state σ_B), Beanstalk’s treasury holds substantial Bean and Bean‑ETH LP balances in the diamond, and BIP‑18 is configured to execute a diamond cut with a malicious init delegatecall.
Single-transaction takeover and treasury drain
The main exploit transaction is:
{
"chainid": 1,
"txhash": "0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7"
}
It is a contract-creation transaction from EOA 0x1c5d... that deploys attacker contract 0x728ad672409da288ca5b9aa85d1a55b803ba97d7 and then executes a complex sequence of calls in a single trace:
// Seed profit transaction evidence snippet (balance_diff.json)
{
"chainid": 1,
"txhash": "0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7",
"native_balance_deltas": [
{
"address": "0x1c5dcdd006ea78a7e4783f9e6021c32935a10fb4",
"before_wei": "27390250384248195067",
"after_wei": "24857169237510444597435",
"delta_wei": "24829778987126196402368"
}
],
"erc20_balance_deltas": [
{
"token": "0xdc59ac4fefa32293a95889dc396682858d52e5db",
"holder": "0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5",
"before": "36084584376516",
"after": "0",
"delta": "-36084584376516",
"contract_name": "Bean"
},
{
"token": "0x87898263b6c5babe34b4ec53f22d98430b91e371",
"holder": "0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5",
"before": "540716100968756904",
"after": "0",
"delta": "-540716100968756904",
"contract_name": "UniswapV2Pair"
},
{
"token": "0xdc59ac4fefa32293a95889dc396682858d52e5db",
"holder": "0x79224bc0bf70ec34f0ef56ed8251619499a59def",
"before": "0",
"after": "36398226924163",
"delta": "36398226924163",
"contract_name": "Bean"
}
]
}
The trace (artifacts/root_cause/seed/1/0xcd31466.../trace.cast.log) and related artifacts show the following steps:
- Flash loans and liquidity routing: Attacker contract
0x728a...takes flash loans and uses Curve and AMM pools (including Bean/LUSD pool0xd652c40fbb3f06d6b58cb9aa9cff063ee63d465dand other DeFi components) to convert borrowed assets into large Bean and Bean‑ETH LP positions. - Silo deposits and root minting: The contract transfers Bean and Bean‑ETH LP to helper/orchestrator contract
0x79224bc0bf70ec34f0ef56ed8251619499a59def, which then calls Beanstalk’sSiloFacet::depositBeansanddepositLPvia the diamondfallback.LibSilomints a very large amount of stalk and roots for0x7922...and callsincrementBipRoots, inflating BIP‑18’s recorded roots tally. - Governance vote and emergency commit: With roots now overwhelmingly concentrated in
0x7922..., the helper contract callsGovernanceFacet::votefor BIP‑18, causing governance to read the updated roots and conclude that BIP‑18 meets quorum and supermajority thresholds. In the same transaction,0x7922...callsGovernanceFacet::emergencyCommit(18), which, seeing thresholds satisfied and timing constraints met, executes the BIP‑18 diamond cut. - Malicious init delegatecall and treasury transfer: BIP‑18’s diamond cut includes an init delegatecall to a malicious contract at
0xE5eCF7.... This init code reads Bean and Bean‑ETH LP balances from the Beanstalk diamond and transfers:36,084,584.376516BEAN (36084584376516units, 6 decimals), and0.540716100968756904BEAN‑ETH Uniswap V2 LP (540716100968756904units, 18 decimals)
from the Beanstalk diamond0xc1e0...to helper contract0x7922..., as shown in the ERC20 deltas above.
- Unwind and profit realization: After treasury assets are moved, the attacker uses further swaps and repayments to unwind flash loans. The native balance deltas show that EOA
0x1c5d...’s ETH balance increases by24,829.778987126196402368ETH (delta_wei = 24829778987126196402368), while gas costs for the transaction are approximately0.337923336129829947ETH. The net result is a large positive profit in ETH terms.
The crucial point is that the governance checks in vote and emergencyCommit rely on roots that can be inflated and then discarded within a single transaction, allowing a flash‑loan‑based takeover of the treasury without any durable stake.
5. Adversary Flow Analysis
Adversary-related cluster and roles
The adversary-related cluster accounts are:
- EOA 0x1c5dcdd006ea78a7e4783f9e6021c32935a10fb4
- Originator of the key governance and profit transactions (
0xf5a6...,0x68cdec0a...,0xcd31466...). - Ultimate recipient of the ETH profit, as shown by native balance deltas in
0xcd31466.../balance_diff.json.
- Originator of the key governance and profit transactions (
- Attacker contract 0x728ad672409da288ca5b9aa85d1a55b803ba97d7
- Created in the profit transaction’s receipt (
contractAddressfield). - Orchestrates flash loans, AMM/Curve swaps, Silo deposits, governance calls, and unwinds; runtime bytecode and disassembly appear under
artifacts/root_cause/data_collector/iter_2/address/1/0x728a....
- Created in the profit transaction’s receipt (
- Helper/orchestrator 0x79224bc0bf70ec34f0ef56ed8251619499a59def
- Receives Bean and Bean‑ETH LP from Beanstalk during the malicious init.
- Holds the large roots tally used to vote for BIP‑18 and is the direct recipient of drained Bean and LP balances in the init delegatecall, as confirmed by
erc20_balance_deltasin0xcd31466.../balance_diff.json.
- Curve Bean/LUSD pool 0xd652c40fbb3f06d6b58cb9aa9cff063ee63d465d
- External protocol contract heavily used in the exploit path, with large Bean and LUSD deltas in
erc20_balance_deltas. - Acts as a liquidity hub for the attacker’s value routing but is not attacker-controlled; it is included as a key stakeholder contract in the flow.
- External protocol contract heavily used in the exploit path, with large Bean and LUSD deltas in
The primary victims are:
- Beanstalk diamond (protocol) at
0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5; - Bean token at
0xdc59ac4fefa32293a95889dc396682858d52e5db; and - Bean‑ETH Uniswap V2 pair at
0x87898263b6c5babe34b4ec53f22d98430b91e371.
These addresses are verified and appear in the collected source and balance diff artifacts.
Lifecycle stages
The attacker’s end-to-end strategy follows three clear stages:
-
Adversary initial Silo funding
- Tx:
0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d(block14595189, Ethereum chainid1). - Mechanism: Silo deposit (
depositBeans). - Effect: EOA
0x1c5d...deposits Bean into the Beanstalk Silo, establishing initial stalk and roots for later governance use. - Evidence:
artifacts/root_cause/data_collector/iter_2/tx/1/0xf5a69898.../trace.cast.logplus SiloFacet/BeanSilo/LibSilo sources.
- Tx:
-
BIP‑18 proposal and configuration
- Tx:
0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f(block14595906, chainid1). - Mechanism:
GovernanceFacet::proposevia diamondfallback. - Effect:
0x1c5d...creates BIP‑18, sets its parameters (including the malicious init delegatecall), and casts an initial vote with roots recorded inAppStorage.Governance. No ERC20 balances move in this transaction. - Evidence:
artifacts/root_cause/seed/1/0x68cdec0a.../metadata.json,.../balance_diff.json,.../trace.cast.log, andartifacts/root_cause/data_collector/iter_1/tx/1/0x68cdec0a.../receipt.jsonshowingProposalandVoteevents.
- Tx:
-
Flash-loan governance takeover and treasury drain
- Tx:
0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7(block14602790, chainid1). - Mechanism: Contract creation + flash loans + Silo deposits + governance calls + treasury drain in a single transaction.
- Effect:
- Attacker contract
0x728a...is deployed. - Flash loans and DeFi routes acquire large Bean and Bean‑ETH LP balances.
- Helper
0x7922...deposits Bean and Bean‑ETH LP into the Silo, minting roots and increasing BIP‑18’s roots tally viaLibSilo. 0x7922...callsGovernanceFacet::votefor BIP‑18 and thenGovernanceFacet::emergencyCommit(18).- The malicious init delegatecall transfers all Bean and Bean‑ETH LP from the Beanstalk diamond to
0x7922.... - Flash loans are repaid and the path is unwound, leaving EOA
0x1c5d...with a large ETH-denominated profit.
- Attacker contract
- Evidence:
artifacts/root_cause/seed/1/0xcd31466.../trace.cast.logand.../balance_diff.json, plus Beanstalk Silo and GovernanceFacet sources underdata_collector/iter_2/contract/1/0xc1e0...and0x448d33....
- Tx:
ACT opportunity characterization
The adversary’s strategy satisfies the ACT definition:
- All used protocols (Beanstalk governance and Silo, Aave, Curve, Uniswap, WETH) are permissionless and callable by any EOA without whitelisting.
- The key governance functions (
propose,vote,emergencyCommit) are not owner- or admin-gated; they are reached through the diamondfallbackwith standard calldata and gas. - The flash-loan and AMM interactions use public liquidity pools and standard interfaces.
- No private oracle, off-chain privileged data, or non-replicable timing assumptions are required beyond observing the public chain state at block
14602790.
Therefore, an unprivileged adversary-related cluster with enough off-chain capital and the ability to replicate the DeFi routing could reproduce the same strategy and success predicate.
6. Impact & Losses
From the profit transaction’s ERC20 deltas in artifacts/root_cause/seed/1/0xcd31466.../balance_diff.json, the Beanstalk diamond 0xc1e0... loses:
- Bean (token
0xdc59ac4fefa32293a95889dc396682858d52e5db)delta = -36084584376516unitsdecimals = 6- Interpreted amount:
36,084,584.376516BEAN.
- Bean‑ETH Uniswap V2 LP (token
0x87898263b6c5babe34b4ec53f22d98430b91e371)delta = -540716100968756904unitsdecimals = 18- Interpreted amount:
0.540716100968756904BEAN‑ETH UNI‑V2 LP tokens.
Matching positive deltas appear for attacker helper contract 0x7922..., confirming that the full Bean and Bean‑ETH LP treasury balances were transferred from the Beanstalk diamond to an adversary-related account.
On the profit side, the native balance deltas for EOA 0x1c5d... in the same transaction show:
before_wei = 27390250384248195067after_wei = 24857169237510444597435delta_wei = 24829778987126196402368
Converting to ETH (1 ETH = 10^18 wei), this corresponds to a gross increase of approximately:
24829778987126196402368 wei / 1e18 ≈ 24829.778987126196402368 ETH
The gas cost, computed as gasUsed * effectiveGasPrice from the receipt, is approximately 0.337923336129829947 ETH. Thus, the transaction yields a clear positive ETH-denominated profit for the adversary, while the Beanstalk protocol loses essentially its entire Bean and Bean‑ETH LP treasury.
This report focuses on these on-chain treasury movements from the Beanstalk diamond to attacker-related addresses. Protocol-wide downstream effects on individual Silo depositors and secondary markets are not quantified here.
7. References
- Beanstalk diamond and governance source (forge clone)
artifacts/root_cause/data_collector/iter_2/contract/1/0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5/source
- Beanstalk Silo/LibSilo source (updateSilo facet)
artifacts/root_cause/data_collector/iter_2/contract/1/0x448d330affa0ad31264c2e6a7b5d2bf579608065/source
- Profit/attack transaction trace and balance diff
- Tx
0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7(Ethereum mainnet, block14602790) artifacts/root_cause/seed/1/0xcd31466.../trace.cast.logartifacts/root_cause/seed/1/0xcd31466.../balance_diff.json
- Tx
- BIP‑18 proposal transaction trace and receipt
- Tx
0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f(Ethereum mainnet, block14595906) artifacts/root_cause/seed/1/0x68cdec0a.../metadata.jsonartifacts/root_cause/seed/1/0x68cdec0a.../balance_diff.jsonartifacts/root_cause/seed/1/0x68cdec0a.../trace.cast.logartifacts/root_cause/data_collector/iter_1/tx/1/0x68cdec0a.../receipt.json
- Tx
- Initial Silo deposit transaction trace
- Tx
0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d(Ethereum mainnet, block14595189) artifacts/root_cause/data_collector/iter_2/tx/1/0xf5a69898.../trace.cast.log
- Tx