← All Posts | findings | December 31, 2024

YieldNest – Invalid decimals used to calculate totalAssets

Paweł Kuryłowicz

Paweł Kuryłowicz

Managing Partner & Smart Contract Security Auditor

The primary outcome of this vulnerability is that the totalAssets variable will be inaccurately calculated, leading to theft of assets or potential losses for users due to undervalued totalAssets.

Vulnerability Details

The processAccounting function is responsible for updating the amount of assets held in the vault. It iterates through all assets associated with the vault, retrieves their rates, and multiplies them by the vault’s balance. However, there are two key issues related to decimal handling in this computation.

function processAccounting() public virtual {
        VaultStorage storage vaultStorage = _getVaultStorage();

        uint256 totalBaseBalance = vaultStorage.countNativeAsset ? address(this).balance : 0;

        AssetStorage storage assetStorage = _getAssetStorage();
        address[] memory assetList = assetStorage.list;
        uint256 assetListLength = assetList.length;
        uint256 baseAssetUnit = 10 ** (assetStorage.assets[asset()].decimals);

        for (uint256 i = 0; i < assetListLength; i++) {
            uint256 balance = IERC20(assetList[i]).balanceOf(address(this));
            if (balance == 0) continue;
            uint256 rate = IProvider(provider()).getRate(assetList[i]);
            totalBaseBalance += balance.mulDiv(rate, baseAssetUnit, Math.Rounding.Floor);
        }

        _getVaultStorage().totalAssets = totalBaseBalance;
        emit ProcessAccounting(block.timestamp, totalBaseBalance);
    }

First, the calculation amount * rate uses the decimal places of the base asset rather than those of the iterated asset. If the decimal configuration of the base asset differs from that of the iterated asset, it can cause the totalAssets value to be incorrect by several orders of magnitude. This error could result in an inflated or deflated totalAssets, depending on whether the base asset has more or fewer decimal places than the iterated asset.

Second, the native asset is added to totalAssets without any adjustments for its decimal places. This can create additional inaccuracies if the base asset’s decimal configuration differs from that of the native asset, again leading to potential inflation or deflation of the totalAssets value.

Attack scenario

An attacker could exploit these decimal-related vulnerabilities to improperly derive assets from the protocol by following these steps:

  1. Assume the Vault has a base asset with 6 decimal places and a native asset with 18
    decimal places, with countNativeAsset set to true.
  2. Several users deposit 990,000 base assets, minting 990,000 shares.
  3. The attacker deposits 10,000 base assets, acquiring 10,000 shares.
  4. The totalAssets now amounts to 1,000,000 tokens.
  5. The attacker sends 0.00001 (1e-5) native asset to the vault.
  6. The attacker executes the processAccounting function. The protocol computes the base asset amount as 1e12 (1,000,000 * 1e6) and the native amount figures at 1e13 (since 18 – 5 = 13), combining to yield a totalAssets of 11,000,000 tokens (1.1e13).
  7. The attacker redeems their 10,000 shares, which are now valued at 11 tokens each, calculated by dividing the 1.1e13 totalAssets by 1e12 shares.
  8. The vault voids the attacker’s shares and transfers 10,000*11=110,000 base assets. Thus, the attacker effectively steals 100,000 base assets for the cost of 0.00001 native asset.

Impact

HIGH – The primary outcome of this vulnerability is that the totalAssets variable will be inaccurately calculated, leading to theft of assets or potential losses for users due to undervalued totalAssets.

Recommendation

  • When calculating the value of ERC20 assets, use the decimal places of the iterated asset, not the base asset’s decimals, in the computation of amount * rate.
  • Normalize the amount of the native asset to align it with the base asset’s decimal places.

References

Join the newsletter now

Please wait...

Thank you for sign up!