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
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 ifself.assets
is zero. Ifself.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
andself.shares
are non-zero. Ifself.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)
orrequire(self.shares > 0)
) means:
- This happens when the vault is fresh (no shares minted yet) or has been completely redeemed by all users, resulting in
- These division-by-zero scenarios are not handled gracefully.
- 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.
self.assets
must be exactly 0 due to vault insolvency or high fees reducing the asset balance to zero.self.shares
must be greater than 0, ensuring the division logic is triggered.
None. This issue arises purely from internal contract state mismanagement.
- A user interacts with a function that calls
_toSharesExact()
or_toAssetsExact()
(e.g., converting assets to shares). - If
self.assets
orself.shares
is zero, the division operation will revert the transaction. - All users attempting similar operations will face locked funds and inability to execute transactions.
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);
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.
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()
.