#46071 [SC-Low] Ultra-low amount of total shares in collateral pool

Submitted on May 24th 2025 at 13:29:59 UTC by @Audittens for Audit Comp | Flare | FAssets

  • Report ID: #46071

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/implementation/CollateralPool.sol

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

    • Protocol insolvency

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Brief/Intro

Using lack of check in the _selfCloseExitTo function it is posssible to reach state of ultra-low (but non-zero) amount of shares in collateral pool, leading to attacks involving incorrect calculations due to rounding.

Vulnerability Details

In the CollaterPool contract assetData.poolTokenSupply and assetData.poolNatBalance represent shares of the pool and balance of the native token, respectively. The following invariant holds across different part of the code: both poolTokenSupply and poolNatBalance should be big enough (greater than MIN_TOKEN_SUPPLY_AFTER_EXIT=MIN_NAT_BALANCE_AFTER_EXIT=1 ether) or be equal to zero. In the _selfCloseExitTo function this invariant maintained as well -- it is checked for the expected updated values:

Later, in this function there is a special case for the situation when maxRedemptionFromAgent is not big enough to cover requiredFAssets:

As mentioned in the comment it is very important to recheck the updated values of natShare and _tokenShare, as this values are substracted from poolNatBalance and poolTokenSupply, respectively. The check for natShare is present, but the check for _tokenShare is missing.

Impact Details

Because of the mentioned scenario it is possible to reach the state of ultra-low token share. This leads to variety of attacks, e. g., donation attack, errors in roundings during calculations of needed collateral and slashes.

Proof of Concept

Proof of Concept

To reach mentioned situation the attacker needs to fill the redemption queue in a way, such that because of the maxRedeemedTickets = Globals.getSettings().maxRedeemedTickets limit on the number of redemption tickets, the natShare value will be equal to poolNatBalance - 1 ether, while the _tokenShare = assetData.poolTokenSupply.mulDiv(natShare, assetData.poolNatBalance); value will decrease significantly.

To make the scenario more concrete:

  • before the call of the _selfCloseExitTo function poolTokenSupply was equal to 1 ether and the poolNatBalance was equal 100 ether;

  • after the mentioned correction of the natShare and _tokenShare values: natShare equals 99 ether;

  • _tokenShare will be corrected to 99/100*poolTokenSupply;

  • in the result the poolTokenSupply divided by a factor of one hundred.

Attacker can peform donation (therefore, increase the poolNatBalance value) and repeat the mentioned sequence of calls. To pass checks in lines 325-327 and 331-333, attacker will make calls in a way, such that at the start of the function, _tokenShare equals to poolTokenSupply.

By repeating mentioned attack the poolTokenSupply will decrease as much as it is required.

Was this helpful?