The Beanstalk $BEAN Exploit
On April 17th, 2022, Beanstalk was exploited for $181m through a flash loan attack on the protocol’s governance mechanism.
This article will explain to you how the exploit was performed, and how process quality issues enabled it, and will also provide you with a comprehensive aggregation of all current reports/analyses surrounding these events.
But first, what is Beanstalk?
Beanstalk is a “Decentralized Credit Based Stablecoin Protocol” launched in August/September 2021. In essence, they aim to be a third-generation stablecoin that dynamically maintains its $USD peg by “consistently
crossing the price of 1 $BEAN over its value peg with increased frequency and less volatility”.
This mechanism aims to gain its users’ confidence by enabling the buying and selling of $BEAN at its value peg, seemingly resulting in an increasingly stable peg as trading intensifies. All in all, the goal is to create and maintain a decentralized currency that maintains itself thanks to its decentralization.
As such, Beanstalk’s decentralization manifests itself in three ways:
- Decentralized price oracle
- Decentralized governance mechanism
- Decentralized credit facility
The governance mechanism is of especial importance, as its decentralized nature led to an unforeseen attack vector.
Beanstalk’s governance mechanism
Understanding Beanstalk’s governance mechanism is crucial to understanding how the attacker performed their exploit.
In short, the governance mechanism consists of three different parts:
- The Silo a.k.a BeanstalkDAO
- The Stalk System
- The Whitelist Assets
The Silo — BeanstalkDAO — is the governing body of the protocol that proposes and votes on the execution of software upgrades. To join, users must deposit any whitelisted asset. Furthermore, there is an incentivize to participate in the Silo to earn passive yields.
The Stalk System is the Silo’s financial incentive. Users must use this system to gain access to the Silo. In addition to governance membership, users can use the Stalk System to deposit whitelisted assets and generate yield. The yield takes two forms, Stalk (non-tradable ERC-20) and Seed (not liquid, non-tradable).
Stalk is the yield-bearing governance token that allows users to participate in DAO votes and even cast proposals (if you have enough Stalk).
Seed is a non-liquid asset that generates additional Stalk every season (hour).
The Whitelist Assets are the assets whitelisted by the Stalk System, and each one can be deposited in the Silo to generate Stalk and Seed.
Currently whitelisted assets include:
- Uniswap BEAN:ETH LP token
- Curve BEAN3CRV-f LP token
- Curve BEAN3LUSD-f LP token
LP token assets generate more Stalk and Seed when deposited in the Silo.
Remember these assets, they will become important in a bit.
Preparing the Exploit
Firstly, on April 16th, 2022, at 08:38:56 AM +UTC, an unknown Ethereum address swapped 73 ETH for 212,858 BEAN on Uniswap v2 (https://etherscan.io/tx/0xfdd9acbc3fae083d572a2b178c8ca74a63915841a8af572a10d0055dbe91d219).
This unknown address is now known as the Beanstalk Flashloan Exploiter (https://etherscan.io/address/ 0x1c5dcdd006ea78a7e4783f9e6021c32935a10fb4).
It is important to note that the exploiter initially withdrew funds from TornadoCash, which they were then able to bridge over via the Synapse Bridge:
Secondly, approximately nine minutes later, the same address deposited the 212,858 BEAN into the Beanstalk Silo (https://etherscan.io/tx/0xf5a698984485d01e09744e8d7b8ca15cd29aa430a0137349c8c9e19e60c0bb9d)
Thirdly, since a proportionate amount of Stalk is immediately generated upon a whitelisted asset deposit, this Silo deposit allowed the address to propose Beanstalk Improvement Proposals (BIP) 18 (https://etherscan.io/tx/0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f/advanced) and 19 (https://etherscan.io/tx/0x9575e478d7c542558ecca52b27072fa1f1ec70679106bdbd62f3bb4d6c87a80d).
BIP-18 was originally left blank, and BIP-19 (exploiter named it InitBip18, we’ll get to that later) contained a verified contract that proposed a $250k donation to the Ukraine wallet address, as well as $10k to the proposer.
Exploit Methodology 1— Flash Loan
Approximately 24h after proposing BIP-18 and 19, the exploiter initiated a flash loan attack on Beanstalk that implemented the following steps:
As seen above, the exploiter flashloaned approximately $1B from Aave in DAI, USDC, BEAN, and LUSD which they promptly converted into 3CRV.
The 3CRV was used to supply one-sided liquidity to the BEAN:3CRV and BEAN:3LUSD liquidity pools on Curve. This allowed the exploiter to receive massive amounts of the aforementioned BEAN3CRV-f and BEAN3LUSD-f which are both whitelisted assets in the Beanstalk Silo.
These assets were then deposited in the Beanstalk Silo which caused the exploiter to immediately receive a proportionate amount of Stalk and Seed a.k.a Beanstalk governance/voting power. Since LP token assets generate the most Stalk and Seed yield per asset deposited, the exploiter generated approximately 70% of all outstanding Stalk in existence.
Having 70% of all Stalk effectively gave the exploiter a 2/3 supermajority vote, which they used to execute the emergencyCommit() function on BIP-18.
Exploit Methodology 2— Governance Mechanisms
Before we detail this exploit, there is an important Beanstalk functionality to mention. Once a BIP is proposed, it requires a minimum of 7 days of voting time before being executed on-chain. This is supposed to act as a pseudo-timelock mechanism to allow proper time to verify the safety of the proposal.
However, the emergencyCommit() function allows a proposal to be immediately executed on-chain following a waiting period of 1 day as opposed to 7:
Emergency vote threshold:
There is one caveat: the emergencyCommit() function, or any emergency governance action, can only be executed by an address that owns >67% of all outstanding Stalk a.k.a a 2/3 supermajority vote:
This is exactly what the exploiter intended to do.
Exploit Methodology 3 — Deception
Now, this is where it gets quite funky.
Before the finality of the 24h wait time required to engage in an emergency governance action, it seemed like BIP-18 was only meant to donate money to the Ukraine wallet.
However, right after the exploiter underwent the flashloan procedures to acquire a Stalk supermajority, they executed emergencyCommit() which resulted in the transfer of all funds from the Beanstalk Silo to the exploiter’s wallet.
But how did this happen? Wasn’t BIP-18 supposed to donate money to the Ukraine address? Well, no. Here is what went down:
- BIP-19’s contract was named InitBip18 to presumably obfuscate the real BIP-18
- BIP-18 was a blank contract, but upon inspecting the txn hash you can see that it uses contract 0xE5eCF73603D98A0128F05ed30506ac7A663dBb69 to _init BIP-18, meaning that this is the address that will execute the proposal
Upon inspecting this address on Etherscan, you will notice that it was created on April 17th, 2022, 12:24:16 PM +UTC — the same time that the flashloan exploiter launched its series of transactions (https://etherscan.io/tx/0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7).
However, how was this contract created at the same time as the flashloan attack was launched, but was also initially in BIP-18 which was proposed 24h prior?
The answer is CREATE2.
In short, CREATE2 is an EVM opcode that allows a smart contract to be deployed at a precomputed address (https://docs.openzeppelin.com/cli/2.8/deploying-with-create2#create2). In this case, the precomputed address was 0xE5eCF73603D98A0128F05ed30506ac7A663dBb69.
Now that we have established how this contract came to be, let’s look at how it drained funds from the Silo:
- 0xE5eCF73603D98A0128F05ed30506ac7A663dBb69 contains code that would immediately be executed once BIP-18 underwent emergencyCommit()
- Since Diamond (I’ll explain that one later) performs an _init on 0xE5eCF73603D98A0128F05ed30506ac7A663dBb69, the underlying code has its functions executed via _calldata (a parameter within Diamond that executes the functions of the contract that was _init)
- This underlying code transfers the Silo’s deposited whitelisted assets to itself. The code can be viewed and decompiled:
While this contract was siphoning funds, it was also simultaneously delegating these calls to the flash loan contract, meaning that the Silo assets were now being called (appropriated) by the latter, as seen here:
At this point, all of this was happening while a flash loan was taking place. After the BIP-18 contract delegated its calls to the flash loan contract, the latter had now acquired the desired Silo assets and could now settle the loan with a profit. The full course of actions by the contract can be visualized as such:
As a result, Beanstalk lost approximately $182m in funds and through accrued debt. Breakdown:
- 36m BEAN ($36m)
- $33m in ETH and $32m in BEAN from ETH-BEAN UNI v2 LP tokens ($65m)
- 79.2M BEAN3CRV-f Curve LP tokens ($79.2m)
- 1.6M BEAN-LUSD Curve LP tokens ($1.6m)
The exploiter ran off with 24,830 WETH in profit — approximately $76m. The remaining $106m was flash loaned back to Aave.
In order to “wash” the stolen funds, the exploiter address started routing the WETH through TornadoCash:
Note: total damages are now far less than $182m since $BEAN lost its peg.
This exploit exposed multiple process quality issues within Beanstalk, which we will outline below.
Process Quality Issue — Governance Model
Beanstalk is built with a governance model that implements the Diamond EIP-2535 standard.
In short, Diamond is an upgradeable smart contract structure similar to a proxy pattern.
As a reminder, a proxy structure typically deploys two contracts:
- The proxy contract — contains the data and users will interact with it. It is also immutable
- The logic contract
This proxy contract contains a fallback function that delegates calls to the underlying logic contract. This effectively allows someone to execute a function call within the logic contract which changes (upgrades) it without modifying the underlying data.
The Diamond structure takes a similar approach, but allows for multiple logic contracts in the form of Facets:
The structure implements a few aspects that facilitated the exploiter’s mission:
- All assets are stored in a single address. This creates a centralized point of failure for protocol funds
- Diamond supports extreme levels of transparency. Users can call the loupe functions to show all functions used by a diamond
- diamondCut() function enables people to add/replace/remove any number of diamond functions and optionally execute a function with delegatecall. The function has a lot of privilege
- emergencyCommit() allows people to “execute a specified bip as passed, create the associated diamond cut with the bip, pause the bip, and reward the proposer with uncompounded rewards” when a Stalk supermajority is reached
- A proposer that executes emergencyCommit() creates a diamond cut and can delegate to an address that will be _init/have its logic executed. This allows proposers to execute anything they want
- No timelock
- emergencyCommit() should be removed, and no one should be able to acquire a Stalk supermajority in the first place
- If Beanstalk wants to keep its governance on-chain, it should create a default proposer address that would route user proposals. This would disable the opportunity for users to _init a malicious address
- Moving to off-chain governance leads to fewer attack vectors like these, but will also spoil your initial decentralization values. Full on-chain decentralization can be tricky since it is relatively new and can inhabit unforeseen attack vectors at any time. You must hedge your bets and decide what is most important to you
- Please implement a timelock. A voting period doesn’t allow users to see exactly what code will execute if a malicious contract is disguised. Timelocks will filter through such contracts
- You need more time to vet proposals, execution thereof, and deployment. Adding time delays for all the above would allow more time to evaluate the security of BIP implementations
- Perhaps migrate to a new Diamond structure that restricts privileged calls. This implies centralization of ownership but also reduces the number of points of failure
Post-Hack Dev Response / Moving Forward
Once the dev team realized that Beanstalk was undergoing an exploit, they immediately paused the protocol:
Soon after, they registered an official complaint to the FBI via their Internet Crime Complaint Center:
In addition, the main Beanstalk dev, Publius, decided to doxx himself. Publius turned out to be three distinct people: Benjamin Weintraub, Brendan Sanderson and Michael Montoya. The trio decided to doxx themselves to commit their identities to their protocol moving on, thus confirming that they had nothing to do with the exploit. Pointing the finger at anonymous devs for exploits is common, so we can understand the motivation.
On the evening of April 18th, 2021, the dev team hosted a Town Hall that detailed the dev team’s plan moving forward. This plan includes:
- Issue pods to raise up to 76mill in a fundraiser
- Make a new pod line that at a minimum will get paid out 1/3 of all new bean mints till it gets paid off
- If it starts at 0 in line, maybe for the first 20million beans they all go to that pod line and maybe a 1/3rd after that
- Start at 0% weather and 0 place in pod line and in a highly expedited fashion have the system run and raise up to 76mill. Weather is a clearing price the market sets
- Weather would increase up to a maximum weather over X amount of days (3?)
- If we only raise 10% of 76mill, then it might make sense for everyone to take a 90% haircut. Beanstalk is instantly scaled to the amount of liquidity attracted
- IF we raise liquidity what do we do? “PoL isnt an ideal mechanism.” If we recollateralize the LP, you can get the Silo back up and running in previous state with a haircut → problem people that lost faith will EXIT.
- We need to offer a way to leave system at a discount
- People would receive ownership of liquidity back, but there’d be some timelock/timevesting on the system where if you wait x days (maybe 100) your funds will now be yours. If you withdraw in the first day you’d only get 1% of total value (1day/100days) → incentivize people to stay in Silo
- Beanstalk assets would be recalculated in the percentage of 76million. If we raise 38mill (50% of total needed) we’d scale back stalk and seeds to 50% of the original value
To sum it all up, the team are working on raising enough founds to make up for all the assets lost during the exploit. After that, they will be restarting their provably effective economy in a vested manner that allows users to exit whenever they feel inclined. The earlier they “ragequit”, the more “haircuts” they will incur, meaning that there is an incentive to wait before withdrawing funds.
Overall, I am very impressed by the development team’s response to this unfortunate incident. Protocols that have lost more have had less gracious responses, and I therefore have a lot of faith in their abilities to get back on track.
- Beanstalk Discord: https://discord.gg/EcaDwQtFkt
- Beanstalk Technical Docs: https://gist.github.com/leonardo-fibonacci/268bb316c4c5ac08bcbc431927998a51#technical-guide
- Beanstalk Town Hall Recording: https://www.youtube.com/watch?v=yXxfBUlN9X4
- Tenderly: https://tenderly.co/
- Ethtx.info: https://ethtx.info/
- Etherscan: https://etherscan.io/