#46771 [SC-Insight] Incorrect Collateral Ratio Check Due to Rounding Error
Submitted on Jun 4th 2025 at 13:34:13 UTC by @TheCarrot for Audit Comp | Flare | FAssets
Report ID: #46771
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/implementation/CollateralPool.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
The _staysAboveCR
function checks whether withdrawing collateral (_withdrawnNat
) keeps the pool's collateral ratio (CR) above a threshold (_crBIPS
). However, due to integer division rounding errors, the check can incorrectly return true even when the CR is slightly below the required threshold. This could allow users to exit the pool while leaving it undercollateralized.
Vulnerability Details
The current implementation checks:
return (_assetData.poolNatBalance - _withdrawnNat) * _assetData.assetPriceDiv >=
(_assetData.agentBackedFAsset * _assetData.assetPriceMul).mulBips(_crBIPS);
Problem:
If (_assetData.poolNatBalance - _withdrawnNat) * _assetData.assetPriceDiv
is just below the required value (due to rounding), the function still returns true.
Example:
Suppose:
poolNatBalance = 1000
_withdrawnNat = 500
assetPriceDiv = 1
agentBackedFAsset * assetPriceMul * crBIPS = 499
(just below500
)
The check
500 >= 499
passes, even though the pool is undercollateralized by 1 wei.
Fix: Replace >=
with >
to add a 1-wei buffer:
return (_assetData.poolNatBalance - _withdrawnNat) * _assetData.assetPriceDiv >
(_assetData.agentBackedFAsset * _assetData.assetPriceMul).mulBips(_crBIPS);
Impact Details
Impact:
Users could withdraw slightly more collateral than allowed, gradually pushing the pool into undercollateralization.
If exploited repeatedly, the pool could become insolvent, causing losses for remaining LPs.
Exploit Scenario:
An attacker monitors the pool’s CR.
When CR is near exitCR, they trigger small exits that round favorably, draining collateral.
The pool eventually becomes undercollateralized, risking bad debt accumulation.
References
https://github.com/flare-foundation/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/implementation/CollateralPool.sol#L602
Proof of Concept
Proof of Concept
Attack Scenario: Gradually Draining Pool Collateral via Rounding Exploit Assumptions:
The pool has a collateral ratio (CR) very close to exitCR (e.g., exitCR = 150%, current CR = 150.0001%).
The attacker holds pool tokens and wants to exit while maximizing collateral extraction.
Step-by-Step Attack Path
Step 1: Monitor Pool State
The attacker tracks the pool’s real-time collateral ratio using:
CR = (poolNatBalance * assetPriceDiv) / (agentBackedFAsset * assetPriceMul)
Waits until CR ≈ exitCR + ε (where ε is a tiny buffer, e.g., 1 wei).
Step 2: Craft a Withdrawal Just Below exitCR
The attacker calculates the maximum _withdrawnNat such that:
(poolNatBalance - _withdrawnNat) * assetPriceDiv == (agentBackedFAsset * assetPriceMul * exitCR) - 1
Due to integer division rounding, the check:
(poolNatBalance - _withdrawnNat) * assetPriceDiv >= (agentBackedFAsset * assetPriceMul * exitCR)
incorrectly returns true (because 499 >= 499 is true, even though CR is technically below exitCR).
Step 3: Execute Exit Transaction
The attacker calls:
exit(_tokenShare, TokenExitType.MAXIMIZE_FEE_WITHDRAWAL)
The function withdraws _withdrawnNat while the pool’s true CR is now slightly below exitCR.
Step 4: Repeat to Drain Collateral
The attacker repeats Steps 1–3 in small increments:
Each withdrawal skims off 1–2 wei below exitCR.
Over time, the pool’s CR degrades further, risking undercollateralization.
Step 5: Trigger Insolvency
Once the pool’s CR drops significantly below exitCR, other LPs panic and exit.
The pool cannot cover all redemptions, leading to bad debt or liquidation.
Was this helpful?