Skip to content

Latest commit

 

History

History
76 lines (59 loc) · 4.43 KB

File metadata and controls

76 lines (59 loc) · 4.43 KB

Lucky Peach Barbel

High

Division by Zero in Asset-to-Share and Share-to-Asset Conversion Functions Causes Complete Vault Operation Freeze and Fund Lock

Root Cause

The issue stems from missing validation checks in the _toSharesExact() and _toAssetsExact() functions within CheckpointLib. These functions rely on the self.assets and self.shares variables to perform calculations involving division. Specifically:

  • In _toSharesExact():

    return assets.muldiv(self.shares, UFixed6Lib.unsafeFrom(self.assets));

    Here, the division assets.muldiv(self.shares, UFixed6Lib.unsafeFrom(self.assets)) does not check if self.assets is zero. If self.assets == 0, the division operation causes a revert due to division by zero.

  • Similarly, in _toAssetsExact():

    return shares.muldiv(UFixed6Lib.unsafeFrom(self.assets), self.shares);

    This function assumes that both self.assets and self.shares are non-zero. If self.shares == 0, the division will revert due to a division by zero error.

  • self.assets Can Be Zero:

    • This occurs when the vault has no assets remaining. This may happen due to high withdrawal fees, mismanagement of vault funds, or external market conditions reducing the collateral.
  • self.shares Can Be Zero:

    • This happens when the vault is fresh (no shares minted yet) or has been completely redeemed by all users, resulting in self.shares == 0. The lack of a guard clause (e.g. require(self.assets > 0) or require(self.shares > 0)) means:
  1. These division-by-zero scenarios are not handled gracefully.
  2. Any function calling _toSharesExact() or _toAssetsExact() will revert, halting execution and preventing user interactions with the vault. This issue is critical because it directly blocks user operations, essentially locking funds in the vault.

Internal Pre-conditions

  1. self.assets must be exactly 0 due to vault insolvency or high fees reducing the asset balance to zero.
  2. self.shares must be greater than 0, ensuring the division logic is triggered.

External Pre-conditions

None. This issue arises purely from internal contract state mismanagement.


Attack Path

  1. A user interacts with a function that calls _toSharesExact() or _toAssetsExact() (e.g., converting assets to shares).
  2. If self.assets or self.shares is zero, the division operation will revert the transaction.
  3. All users attempting similar operations will face locked funds and inability to execute transactions.

Impact

toSharesGlobal()

return self.assets.lte(Fixed6Lib.ZERO) ?
    assets.unsafeSub(settlementFee) :
    _toShares(self, assets).unsafeSub(_toSharesExact(self, settlementFee));

If self.assets == 0, _toSharesExact() will revert. This blocks user deposits from being converted into shares, effectively halting all deposit operations globally. toAssetsGlobal() If self.shares == 0, _toAssetsExact() will revert. This blocks users from converting shares back to assets, freezing all redemption operations globally.

return (self.shares.isZero() ? shares : _toAssets(self, shares)).unsafeSub(settlementFee);

complete()

self.shares = self.shares.add(profitShares);
...
profitShares = profitAssets.mul(self.shares).div(UFixed6Lib.unsafeFrom(self.assets).sub(profitAssets));

If self.assets == 0, the profit-sharing calculation in complete() will revert. This prevents the checkpoint from finalizing, locking all state updates and stopping vault operations.


Mitigation

Add require(self.assets > 0, "Assets cannot be zero") and require(self.shares > 0, "Shares cannot be zero") checks before performing division in _toSharesExact() and _toAssetsExact().