Full Report
ERC4626 is a specification for a vault. This vault gives out shares in return for assets. The shares determine the underlying assets that can be redeemed (in terms of assets) from the protocol. The number of shares that are gained is dependent on the vaults exchange rate. This rate is dependent on the vaults liquidity. For instance, if 100 tokens give 200 shares, then the share is worth 0.5 of a token. When depositing tokens, the user receives a slightly rounded down number of shares. This often doesn't matter... but, in the case where small amounts of assets and shares are being used, this can cause major havoc. For instance, if the exchange rate for is 1 share for 100 tokens. Then depositing 99 tokens would result in a drastic loss with the user getting zero shares. When the vault is empty, an attacker can abuse this rounding to steal money from other users. For instance, take the following scenario: The attacker buys a single share for virtually no money. A regular user decides to add in a large amount of the asset. They send the transaction. The transaction is frontran by an attacker who donates the same amount of funds as the user is about to add in. Why does this matter? The contract currently has 1 share for a large amount of assets. So, the cost of 1 share is the amount of assets added in (assets worth/1 share). The user deposits in the token. If it's less than the worth of the one share, then they will get 0 shares even though they liquidity is added. The attacker can then withdraw their share for a massive profit. This is a known limitation of the specification that was first realized by a Trail of Bits audit of Yearn. How do we prevent this? Two obvious ways are forcing a large deposit at the beginning of the contract and forcing a minimum amount of shares. The new concept being used to prevent this by Amxx and Open Zeppelin is a virtual offset. The issue occurs because of an inflation attack on the contract and rounding errors with a small amount of shares. The goal of the virtual offset is to artificially increase the number of shares by adding more decimals to the shares. This mitigates the attack since the rounding error is what made the attack possible The second remediation strategy is including the virtual shares and virtual assets in the exchange rate computation. By doing this artificial increase, the donation is somewhat put into the virtual components, making it not profitable. Overall, an interesting attack that is possible because of frontrunning and the EVM not supporting floating point numbers. The solution to the problem is clever and does cost very much money for the users of the vault.
Analysis Summary
# Vulnerability: ERC4626 Inflation Attack (Rounding Error Exploitation)
## CVE Details
- **CVE ID**: N/A (General design pattern vulnerability)
- **CVSS Score**: Estimated 7.5 (High) - Primary impact is on Integrity/Assets.
- **CWE**: CWE-682 (Incorrect Calculation), CWE-190 (Integer Overflow or Wraparound)
## Affected Systems
- **Products**: Smart contracts implementing the ERC4626 Tokenized Vault Standard.
- **Versions**: Early implementations of ERC4626 (e.g., Yearn V3 early drafts, OpenZeppelin Contracts < 4.9.0).
- **Configurations**: Vaults with low initial liquidity or those that do not account for "dead shares" or virtual liquidity.
## Vulnerability Description
The vulnerability arises from the way ERC4626 vaults calculate the exchange rate between underlying assets and vault shares. Specifically, the standard uses integer division which rounds down.
An attacker can exploit this by:
1. Being the first to deposit into an empty vault (minting a tiny amount of shares).
2. "Donating" a large amount of assets directly to the vault contract without minting shares.
3. This artificially inflates the value of a single share.
4. When a subsequent user deposits, the rounding logic results in them receiving zero shares for their deposit, effectively sending their assets to the vault (and by extension, the attacker who holds the existing shares).
## Exploitation
- **Status**: PoC available; broadly discussed in security audits (e.g., Trail of Bits, OpenZeppelin).
- **Complexity**: Medium (Requires precise timing and frontrunning capabilities).
- **Attack Vector**: Network (Blockchain/Smart Contract interaction).
## Impact
- **Confidentiality**: None
- **Integrity**: High (Unauthorized manipulation of asset balances and share distribution).
- **Availability**: None
## Remediation
### Patches
- **OpenZeppelin Contracts v4.9.0+**: Introduced the `_decimalsOffset()` mechanism and virtual shares/assets to mitigate the rounding error.
- **ERC4626 Updated Implementations**: Adoption of the "Virtual Offset" method to ensure the exchange rate cannot be easily manipulated by small-balance attackers.
### Workarounds
- **Initial Liquidity Burn**: Manually deposit a small amount of assets and "burn" the resulting shares (sending them to a null address) to ensure the vault is never empty.
- **Minimum Deposit Limits**: Enforcing a minimum amount of assets that can be deposited to ensure the share calculation never rounds to zero.
## Detection
- **Indicators of Compromise**: Unusually high asset-to-share ratios; transactions where a user deposits assets but receives zero shares.
- **Detection Methods and Tools**:
- **Static Analysis**: Tools like Slither or Aderyn can be configured to detect standard ERC4626 implementations lacking rounding protections.
- **Formal Verification**: Checking the `previewDeposit` and `previewRedeem` functions for rounding edge cases.
## References
- hxxps://github[.]com/OpenZeppelin/openzeppelin-contracts/pull/3979
- hxxps://v9.docs.openzeppelin[.]com/contracts/4.x/api/token/erc4626
- hxxps://x[.]com/bytes032/status/1644261171787169794