Arbitrary Execution recently introduced the Web3 Secure Development Life Cycle (SDLC) to the Web3 community. Since then, a couple of large hacks have occurred. We thought it was a great time to focus on one of these hacks through a secure development lens. First, let’s briefly review the recent Euler Finance hack, which totaled $197M. Then, with the benefit of hindsight, we can take another look at how the development process could have been better secured.
Background of Hack
On July 7, 2022, a new Euler improvement proposal was submitted, eIP-14. This proposal added a number of changes to the protocol, including:
- Enabling gas-efficient flash loans
- Adding a donateToReserves function
- Enabling better simulations of batch execution
- Adding Chainlink pricing support
The code audit was split between the Omniscia and Sherlock audit teams, both focusing on different portions of the overall eIP-14 changeset. On August 18, 2022, a Snapshot passed enabling inclusion of the eIP-14 changes. The changes were then deployed on chain.
On March 13, 2023, an attacker hacked Euler Finance by exploiting a vulnerability in the donateToReserves function. The Euler protocol tokenizes collateral and debt using EToken and DToken type objects, respectively. The newly included donateToReserves function allows the caller to transfer any number of their ETokens to the pool’s reserves. The vulnerable donateToReserves function did not check if the transfer would cause the account to become undercollateralized, or placed in a liquidatable state. The attacker, using donateToReserves, placed the account in a state where the DToken balance exceeded that of ETokens. Using a second contract, the attacker liquidated the undercollateralized account, obtaining discounted DTokens which enabled full debt repayment plus an excess number of ETokens—the excess ETokens being the stolen funds.
On March 13, 2023, external blockchain monitors like PeckShield and Forta detected the hack. That same day Sherlock, the company who audited that section of code, paid out a $3.4 million insurance claim to Euler Finance.
Secure Development Improvements
The Euler Finance protocol is a mature project. They implement many secure development practices that add additional defense to many existing threats. But with any project, improvements can be made. Let’s take a look at some additional secure development steps that could have been taken to help prevent this hack.
The Web3 SDLC outlines four phases of protocol development: Design, Develop, Deploy, and Defend. Each of these phases adds a layer of protection to a protocol. Using the Web3 Security Maturity Model, we can identify some areas of improvement in each phase of the development lifecycle.
As a refresher, teams in the Design phase of the Web3 SDLC architect and evaluate project ideas in terms of systemic risk.
A state diagram showing the relationship between EToken, DToken, and deposited collateral could have made it apparent to developers and auditors that changes to EToken balance should have a direct effect on outstanding DToken.
An unclear design of the donateToReserves feature helped contribute to the vulnerability. From the designer:
donateToReserves feature: This allows users to donate some of their ETokens to the reserves. This may be useful for certain wrapper contracts, and potentially could be a better way to burn “dust” in an account (for gas refund purposes).
This statement is essentially the entire design for the new donateToReserves feature. It seems that the intent of this feature is to allow a user to donate a small amount to the reserves. A specific amount should have been defined in this design.
Design Patterns and Architecture
The protocol lacks primitives to operate on market reserves. Outside of initialization and EToken.sol::donateToReserves(), reserves are modified in two different locations: BaseLogic.sol::increaseReserves() and Governance.sol::convertReserves(). This is in contrast to the other account primitives located in BaseLogic.sol. A simple transfer of ETokens to the special reserves account required duplication of code from two sources. This bug is effectively a copy/paste error in which a check for liquidity checkLiquidity() was not added.
In the Develop phase of the Web3 SDLC, teams transform ideas into secure implementations.
There is insufficient testing of the donateToReserves function. The following basic functionality tests were provided:
- basic functionality: donate 1 EToken, wallet has 10
- support for alt tokens: donate of 1 EToken using non-18 decimal token
- basic functionality: sweep, donate max uint, wallet has 10 EToken
- basic functionality: insufficient balance, donate 11 EToken, wallet only has 10
These tests excluded scenario testing of general protocol health. The following is an example of a simple test which will catch the donateToReserves bug. The test donates all of an account’s EToken, as done in another test, but places the account in a different state:
More importantly, the testing framework should have various account and protocol scenarios available as test fixtures against which developers can easily write tests.
The protocol repository lacks automated testing. It is important that any software development projects perform as many checks as possible to ensure code quality before merging into main. There is currently a pull request which would enable these automated tests.
In the Defend phase of the Web3 SDLC, teams continuously monitor the live system for operational irregularities and vulnerabilities.
The Euler protocol team lacked sufficient on-chain monitoring. Protocol developers and maintainers understand the intended behavior of their deployed contracts. They can therefore devise well-tuned monitoring, which could alert on suspicious behavior and potentially allow the protocol to take preventive measures. This becomes most apparent when an external, general purpose PeckShield monitor was able to identify the hack.
The Euler Protocol implements many of the metrics outlined by the SMM. But, there are key areas where improvements can be made, particularly in Design and Development. As we’ve seen in the eIP-14 proposal, it is important that protocol contributors are able to easily design and implement updates with full understanding of their effects on the overall protocol. This means added scenario testing and design diagrams. Similarly, with these improvements, proposal voters and auditors can more readily understand the impact of changes to the overall protocol.
More generally, changes need to be made with regard to how audits are conducted. There is a common fallacy in Web3, that X number of audits is equivalent to the codebase being audited X times. In reality, each audit is a snapshot-in-time review and these do not necessarily stack. Focus can shift towards new features, and bugs can still slip through the cracks. Audits are still important, and a great way to shake out deep logic bugs in code, but protocol teams need to do more than simply rely on audits.
Arbitrary Execution is the right choice to perform your smart contract audits, and to become your security partner at every step of the development lifecycle. Contact us for a security consultation and download the Web3 SDLC whitepaper to learn more.