ERC-4626 Vulnerabilities and How to Avoid Them in Your Project

ERC-4626 Vulnerabilities and How to Avoid Them in Your Project

·

4 min read

ERC-4626 is an important standard used to create tokenised vaults. It's built similarly to the popular ERC-20 token standard, which makes it easier to use. Below we’ll discuss some of the core functionality and also how these play into some of the security concerns.

The ERC-4626 standard offers a solid structure for developers looking to establish tokenised vaults within the DeFi ecosystem. Its functions allow for seamless identification and quantification of assets, and interaction with these vaults. It also makes composability and interoperability frictionless since vaults are now standardised across protocols.

The main security risks associated with the implementation of ERC-4626 are inflation attacks, oracle manipulation and logic vulnerabilities which have caused huge losses for protocols and users alike in recent hacks.

Inflation Attacks

Inflation attacks occur when attackers add assets to increase the value of shares, which can hurt other users who deposit after. To protect against this, developers can protect vaults by minting a small amount of the token upon initialisation. This significantly reduces the amount an attacker can steal from the vault. For example, Uniswap mints 1000 WEI of LP tokens for every new pool it creates a token pair for. This ensures total supply is greater than zero making an attack significantly less impactful.

Oracle Manipulation

Another problem that arises with ERC-4626 vaults is that the underlying token decimals can differ from the token decimals of the ERC-20 vault token itself. Each vault has an ERC-20 that represents the amount of shares a user has of that vault. However, if the ERC-20 to the vault uses 18 decimals and the underlying token uses 6 decimals such as USDC or USDT, then you can run into issues when calculating the price of this asset. If you use the ERC-20 of the vault as an oracle to price the underlying token you will now have a value that is 1e18 instead of 1e6.

In the below example the getPrice function is using the ERC-4626 decimals:

function getPrice(address token) external view returns (uint) {
        uint decimals = IERC4626(token).decimals();
        return IERC4626(token).previewRedeem(
            10 ** decimals
        ).mulDivDown(
            oracle.getPrice(IERC4626(token).asset()),
            10 ** decimals
        );
    }

This will cause an incorrect price to be returned if the underlying asset has different decimals than the shares token itself:

function getPrice(address token) external view returns (uint) {
        uint decimals = IERC4626(token).decimals();
        address underlyingToken = IERC4626(token).asset();
        return IERC4626(token).previewRedeem(
            10 ** decimals
        ).mulDivDown(
            oracleFacade.getPrice(underlyingToken),
            10 ** IERC20Metadata(underlyingToken).decimals()
        );
    }

Causing a lot of issues for your users as well as anyone building on top of your vault as they are meant to be highly composable. For this reason proper decimals scaling is extremely important when it comes to ERC-4626 vaults.

Logic Vulnerabilities

In order to avoid logic flaws it’s important to keep track of assets and shares by always verifying their accuracy.

There are two main methods to deposit assets and get shares: deposit and mint. With deposit, you tell the contract how many assets you want to give, and it tells you how many shares you get. With mint, you say how many shares you want, and it tells you how many assets it needs.

function deposit(uint256 assetAmount, address userAddress) public returns (uint256) {
    // Logic for depositing assets and getting shares
}

function mint(uint256 shareAmount, address userAddress) public returns (uint256) {
    // Logic for giving the number of shares and getting the needed assets
}

It’s paramount to always check your shares after a transaction:


uint256 startShareBalance = erc4626.balanceOf(address(this));
uint256 gotShares = erc4626.deposit(assetAmount, address(this));
assert(erc4626.balanceOf(address(this)) >= startShareBalance + gotShares);

This makes sure the number of shares after the transaction matches what's expected, keeping everything transparent and trustworthy.

Conclusion

The ERC-4626 standard is an essential pillar of DeFi infrastructure, bringing composability and interoperability to vaults. When best practice is followed and a few simple key security concepts are kept in mind, catastrophic losses caused by oracle manipulation, inflation attacks or logic hacks can often be avoided.


Rivanorth is a global boutique Web3 cybersecurity company. We specialise in smart contract audits and blockchain security advisory. Visit X for the latest updates.

You build the future. We help you secure it.