All incidents

Beanstalk flash-loan governance takeover drains treasury assets

Share
Apr 17, 2022 12:24 UTCAttackLoss: 36,084,584.38 BEAN, 0.54 BEAN-ETH_UNI-V2Manually checked1 exploit txWindow: Atomic
Estimated Impact
36,084,584.38 BEAN, 0.54 BEAN-ETH_UNI-V2
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Apr 17, 2022 12:24 UTC → Apr 17, 2022 12:24 UTC

Exploit Transactions

TX 1Ethereum
0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7
Apr 17, 2022 12:24 UTCExplorer

Victim Addresses

0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5Ethereum

Loss Breakdown

36,084,584.38BEAN
0.540716BEAN-ETH_UNI-V2

Similar Incidents

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 incrementBipRoots in AppStorage.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:

  1. Borrow capital via flash loans;
  2. Convert that capital into Bean and Bean‑ETH LP;
  3. Deposit those assets into the Silo;
  4. Mint a very large number of roots and have them counted toward BIP‑18’s roots tally; and
  5. 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:

  • propose creates a BIP, sets its parameters, and records an initial vote and roots for the proposer based on current Silo roots; and
  • vote and emergencyCommit check quorum and supermajority thresholds using the current roots stored in AppStorage.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 (block 14595189) is an adversary-crafted deposit from EOA 0x1c5dcdd006ea78a7e4783f9e6021c32935a10fb4 into the Beanstalk Silo.
    The trace (artifacts/root_cause/data_collector/iter_2/tx/1/0xf5a69898.../trace.cast.log) shows calls into SiloFacet and LibSilo, establishing initial stalk and roots for 0x1c5d....

Next, the attacker created and configured BIP‑18:

  • Transaction 0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f (block 14595906) is a GovernanceFacet::propose call from 0x1c5d... routed via the diamond’s fallback.
    The associated metadata and receipt (artifacts/root_cause/seed/1/0x68cdec0a.../metadata.json, .../balance_diff.json, .../trace.cast.log, and artifacts/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 for 0x1c5d... in AppStorage.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:

  1. Flash loans and liquidity routing: Attacker contract 0x728a... takes flash loans and uses Curve and AMM pools (including Bean/LUSD pool 0xd652c40fbb3f06d6b58cb9aa9cff063ee63d465d and other DeFi components) to convert borrowed assets into large Bean and Bean‑ETH LP positions.
  2. Silo deposits and root minting: The contract transfers Bean and Bean‑ETH LP to helper/orchestrator contract 0x79224bc0bf70ec34f0ef56ed8251619499a59def, which then calls Beanstalk’s SiloFacet::depositBeans and depositLP via the diamond fallback. LibSilo mints a very large amount of stalk and roots for 0x7922... and calls incrementBipRoots, inflating BIP‑18’s recorded roots tally.
  3. Governance vote and emergency commit: With roots now overwhelmingly concentrated in 0x7922..., the helper contract calls GovernanceFacet::vote for BIP‑18, causing governance to read the updated roots and conclude that BIP‑18 meets quorum and supermajority thresholds. In the same transaction, 0x7922... calls GovernanceFacet::emergencyCommit(18), which, seeing thresholds satisfied and timing constraints met, executes the BIP‑18 diamond cut.
  4. 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.376516 BEAN (36084584376516 units, 6 decimals), and
    • 0.540716100968756904 BEAN‑ETH Uniswap V2 LP (540716100968756904 units, 18 decimals)
      from the Beanstalk diamond 0xc1e0... to helper contract 0x7922..., as shown in the ERC20 deltas above.
  5. 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 by 24,829.778987126196402368 ETH (delta_wei = 24829778987126196402368), while gas costs for the transaction are approximately 0.337923336129829947 ETH. 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.
  • Attacker contract 0x728ad672409da288ca5b9aa85d1a55b803ba97d7
    • Created in the profit transaction’s receipt (contractAddress field).
    • 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....
  • 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_deltas in 0xcd31466.../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.

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:

  1. Adversary initial Silo funding

    • Tx: 0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d (block 14595189, Ethereum chainid 1).
    • 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.log plus SiloFacet/BeanSilo/LibSilo sources.
  2. BIP‑18 proposal and configuration

    • Tx: 0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f (block 14595906, chainid 1).
    • Mechanism: GovernanceFacet::propose via diamond fallback.
    • Effect: 0x1c5d... creates BIP‑18, sets its parameters (including the malicious init delegatecall), and casts an initial vote with roots recorded in AppStorage.Governance. No ERC20 balances move in this transaction.
    • Evidence: artifacts/root_cause/seed/1/0x68cdec0a.../metadata.json, .../balance_diff.json, .../trace.cast.log, and artifacts/root_cause/data_collector/iter_1/tx/1/0x68cdec0a.../receipt.json showing Proposal and Vote events.
  3. Flash-loan governance takeover and treasury drain

    • Tx: 0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7 (block 14602790, chainid 1).
    • 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 via LibSilo.
      • 0x7922... calls GovernanceFacet::vote for BIP‑18 and then GovernanceFacet::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.
    • Evidence: artifacts/root_cause/seed/1/0xcd31466.../trace.cast.log and .../balance_diff.json, plus Beanstalk Silo and GovernanceFacet sources under data_collector/iter_2/contract/1/0xc1e0... and 0x448d33....

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 diamond fallback with 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 = -36084584376516 units
    • decimals = 6
    • Interpreted amount: 36,084,584.376516 BEAN.
  • Bean‑ETH Uniswap V2 LP (token 0x87898263b6c5babe34b4ec53f22d98430b91e371)
    • delta = -540716100968756904 units
    • decimals = 18
    • Interpreted amount: 0.540716100968756904 BEAN‑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 = 27390250384248195067
  • after_wei = 24857169237510444597435
  • delta_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, block 14602790)
    • artifacts/root_cause/seed/1/0xcd31466.../trace.cast.log
    • artifacts/root_cause/seed/1/0xcd31466.../balance_diff.json
  • BIP‑18 proposal transaction trace and receipt
    • Tx 0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f (Ethereum mainnet, block 14595906)
    • artifacts/root_cause/seed/1/0x68cdec0a.../metadata.json
    • artifacts/root_cause/seed/1/0x68cdec0a.../balance_diff.json
    • artifacts/root_cause/seed/1/0x68cdec0a.../trace.cast.log
    • artifacts/root_cause/data_collector/iter_1/tx/1/0x68cdec0a.../receipt.json
  • Initial Silo deposit transaction trace
    • Tx 0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d (Ethereum mainnet, block 14595189)
    • artifacts/root_cause/data_collector/iter_2/tx/1/0xf5a69898.../trace.cast.log