The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: low
Valid

Precision loss in `LiquidationPool::distributeAssets` could result in rewards not being paid to stakers with small EURO balances

Description

LiquidationPool::distributeAssets is called when liquidating a vault. If the value of asset.amount * _positionStake is less than stakeTotal and of the same or smaller order of magnitude, _portion will round down to zero.

The portion per staker is calculated using the following calculation:

uint256 _portion = asset.amount * _positionStake / stakeTotal;

For a collateral token with only a few decimals, this could cause small stakers not to receive rewards for small liquidations.

rewards[abi.encodePacked(_position.holder, asset.token.symbol)] += _portion;

Since the entire pool could be large, with both TST and EURO balances contributing to the stake total sum, a single staker could be proportionally small despite having staked a significant amount of EURO. Furthermore, given the vault debt is reset to zero after liquidation, it could be the case that the loan is considered liquidated without being fully repaid.

Impact

Proportionally small stakers will receive no liquidation rewards. In some instances, if the liquidation value is sufficiently small or the total staked is sufficiently large, potentially very few holders will receive rewards, but the "liquidated" vault will cause the protocol to accrue bad debt. Therefore, this is a medium-severity finding.

Proof of Concept

Consider a low-decimal token such as GUSD which could later be added as accepted collateral and only has two decimals of precision:

❯ chisel
Welcome to Chisel! Type `!help` to show available commands.
➜ uint256 amount = 900e2;
➜ uint256 stake = 100e18;
➜ uint256 totalStake = 10_000_000e18;
➜ uint256 position = amount * stake / totalStake;
➜ position
Type: uint256
├ Hex: 0x0
├ Hex (full word): 0x0
└ Decimal: 0
❯ python -c "print(900e2*100e18/10000000e18)"
0.8999999999999999

This can also be demonstrated for WBTC which is currently listed as an accepted collateral and has eight decimals of precision:

❯ chisel
Welcome to Chisel! Type `!help` to show available commands.
➜ uint256 amount = 0.05e8;
➜ uint256 stake = 10e18;
➜ uint256 totalStake = 100_000_000e18;
➜ uint256 position = amount * stake / totalStake;
➜ position
Type: uint256
├ Hex: 0x0
├ Hex (full word): 0x0
└ Decimal: 0
❯ python -c "print(0.05e8*10e18/100_000_00e18)"
5.0

Recommended Mitigation

Modify the liquidation portion calculations to work on values already scaled up to 18 decimals of precision.

uint256 _portion = asset.amount * 10 ** (18 - asset.token.dec) * _positionStake / stakeTotal;
Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

precision

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.