On April 16th, 2023, Hundred Finance, a Compound V2 fork, was attacked, leading to a loss of about $7.4 million. The attack involved two main issues:
- A precision loss issue (an incorrect rounding issue);
- A empty markets, which allowed the hacker to manipulate the exchangeRate.
Compound V2, the most forked lending protocol according to DeFiLlama, boasts over 100 forks. Audited by OpenZeppelin and proven in battle, its contracts are deemed secure. Yet, the Hundred Finance attack highlighted how precision loss, especially in low liquidity, can critically affect DeFi protocol security, triggering a wave of similar exploits across notable forks like Midas and Radiant.
For a deeper understanding, you are encouraged to consult the full analysis. Below, we provide a succinct introduction to this incident, highlighting it as one of the top ten security incidents of 2023.
Background
Overview of Hundred Finance
Hundred Finance is a fork of Compound v2 that operates across multiple mainnets, utilizing Chainlink oracles. Unlike traditional finance lending practices, DeFi lending protocols like Compound and Aave do not allow for over-collateralized borrowing. Simply put, if you deposit $100 worth of Token A, you can only borrow assets worth less than $100. According to the risk control coefficients of most protocols, this ratio typically ranges from 50% to 80%.
For lending protocols, common attack methods include price manipulation and reentrancy. Interestingly, Hundred Finance had previously experienced a reentrancy attack in March 2022, but this incident created a new attack vector.
hToken
Lending protocols that fork from Compound and Aave create a corresponding accounting token for each underlying token (collateral). For Hundred Finance:
- USDC corresponds to hUSDC
- WBTC corresponds to hWBTC
The exchange rate between underlying token and hToken is referred to the exchangeRate
.
- When depositing assets, users should call the
mint()
of the hToken. - When withdraw assets, user should call the
redeem()
of the hToken.
exchangeRate
The following is the formula for calculating the exchangeRate
:
Where:
getCash()
: The amount of underlying token balance owned by this hToken contract. This is a key parameter that can be manipulated. Please remember this.totalBorrows()
: The amount of underlying token currently loaned out by the market, and the amount upon which interest is accumulated to suppliers of the market.totalReserves()
: Reserves are an accounting entry in each hToken contract that represents a portion of historical interest set aside as cash which can be withdrawn or transferred through the protocol’s governance.totalSupply()
: The number of tokens currently in circulation in this hToken market.
Note: The exchangeRate
between a hToken and the underlying asset e.g. (dai vs hDai or eth vs hEth) begins at 0.020 and increases at a rate equal to the compounding market interest rate.
Liquidation
To prevent bad debt, lending protocols allow any user to liquidate another user's debt. Let's use the following example to illustrate:
- Alice deposits BTC worth $100 and borrows ETH worth $70.
- If the price of ETH rises or the price of BTC falls, Alice's assets may reach the liquidation threshold.
- Bob can use a certain amount of ETH to liquidate Alice's BTC, bringing Alice's debt back to a healthy level to ensure the safety of the protocol's funds (the protocol rewards users who initiate liquidation).
Liquidation is not the focus of this attack, but it was involved in the attack. Here, we only need to know that any user can liquidate another user's debt with one type of Token, which will reduce the corresponding hToken.
The Vulnerability
Precision Loss Issue
The hacker makes that when collateral is withdrawn through redeem()
, the calculation for the amount of hToken to be deducted results in 1.99999992 (very close to 2 but less than 2). When converting to an integer in truncate()
, the use of rounding down leads to a final result as 1.
exchangeRate
The calculation of the exchangeRate
, as previously introduced, involves getCash()
, which refers to the amount of underlying balance owned by the hToken contract. By directly transferring underlying tokens into the contract (without mint
, just transferring), the hacker can manipulate the exchangeRate
.
However, it's important to note that this exchangeRate issue alone does not compromise security of the protocol; Hackers cannot profit from it in isolation. In the context of this attack, it was primarily exploited to amplify the hacker's gains, allowing them to rapidly deplete the pool. Otherwise, the attack shifts from a single, decisive strike to a series of minor stabs, requiring numerous iterations to have a significant impact.
To be brief, the precision loss is key issue in this attack.
The Attack Process
Here is the attack transaction, and we will now use the Phalcon Explorer to analyze this complex transaction.
Transaction: 0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451
-
Borrow 500 WBTC from Aave V3 through a Flashloan.
-
Redeem
all the previously acquired hWBTC, reset hWBTC'stotalSupply
to 0.
The objective before is prepare reserve funds using a flashloan and to reset hWBTC as a new market.
-
Create
the second attack contract (hereafter referred to as Attack Contract 2) and transfer all WBTC (500.30063816 WBTC) to Attack Contract 2. -
Mint()
hWBTC using 4 WBTC to produce 200 hWBTC. -
Redeem()
199.99999998 hWBTC, leaving the hWBTC total at 0.00000002 (2 wei hWBTC). -
Transfers all WBTC (500.30063816 WBTC) into hWBTC. Note, direct transfers do not increase hWBTC; it can be seen as donating WBTC to the pool. The main purpose of this step is to manipulate the
exchangeRate
mentioned earlier. At this point, hWBTC'stotalSupply
remains at 2 wei hWBTC, but there are now 500.30064194 WBTC, making theexchangeRate
hundreds of times the original. -
Borrow 1021 Ether from the hETH market.
-
Redeem()
50030063815 WBTC, where, after calculation, 1.9999992 hBTC should be deducted, but due to precision loss, only 1 hBTC is deducted, creating a significant precision loss (nearly 50%). At this point, the hacker has 500 WBTC + 1021 Ether, successfully profiting 1021 Ether. -
The attacker
liquidate()
the remaining hWBTC, resetting itstotalSupply
to 0, in preparation for continuing attacks on other markets. Given that almost all WBTC within hWBTC has been withdrawn, the hacker manages this with just 0.000002 Ether. -
Continue to attack other markets, draining the entire protocol.
-
Repay the Flashloan to Aave.
Security Recommendations
Mitigations for Lending Protocol
This issue is prevalent, particularly for forks of Compound and Aave. A proactive approach involves, when launching new markets, minting some accounting token to ensure the totalSupply
never goes to 0.
Mitigations for Precision Loss Issue
To better circumvent the series of issues due to precision loss, setting a minimum value is an effective method in practice. This strategy helps to avoid the significant impact caused by precision loss when dealing with very small values.
Read other articles in this series:
- Lead-In: Top Ten "Awesome" Security Incidents in 2023
- #1: Harvesting MEV Bots by Exploiting Vulnerabilities in Flashbots Relay
- #2: Euler Finance Incident: The Largest Hack of 2023
- #3: KyberSwap Incident: Masterful Exploitation of Rounding Errors with Exceedingly Subtle Calculations
- #4: Curve Incident: Compiler Error Produces Faulty Bytecode from Innocent Source Code
- #5: Platypus Finance: Surviving Three Attacks with a Stroke of Luck
- #7: ParaSpace Incident: A Race Against Time to Thwart the Industry's Most Critical Attack Yet
- #8: SushiSwap Incident: A Clumsy Rescue Attempt Leads to a Series of Copycat Attacks
- #9: MEV Bot 0xd61492: From Predator to Prey in an Ingenious Exploit
- #10: ThirdWeb Incident: Incompatibility Between Trusted Modules Exposes Vulnerability