Root + Impact
Description
**Description:** when ever a user calls `claimSurvived` and `claimExpired`, the bonus share calculated from `ConfidencePool::_bonusShare` function did some rounding down in division due to solidity maths.
Those remaining will stay in the pool forever, in some valuable assets the dust means a lot.
```solidity
function _bonusShare(address u, uint256 userEligible) internal view returns (uint256) {
if (snapshotTotalBonus == 0) return 0;
if (riskWindowStart == 0) return 0;
uint256 T = outcomeFlaggedAt;
uint256 userPlus = T * T * userEligible + userSumStakeTimeSq[u];
uint256 userMinus = 2 * T * userSumStakeTime[u];
uint256 userScore = userPlus > userMinus ? userPlus - userMinus : 0;
uint256 plus = T * T * snapshotTotalStaked + snapshotSumStakeTimeSq;
uint256 minus = 2 * T * snapshotSumStakeTime;
uint256 globalScore = plus > minus ? plus - minus : 0;
if (globalScore == 0) {
if (snapshotTotalStaked == 0) return 0;
@> return Math.mulDiv(userEligible, snapshotTotalBonus, snapshotTotalStaked);
}
@> return Math.mulDiv(userScore, snapshotTotalBonus, globalScore);
}
```
Risk
Likelihood:
Impact:
Proof of Concept
**Proof of Concept:**
<details>
<summary>Code</summary>
```solidity
pragma solidity 0.8.26;
import {IConfidencePool} from "src/interfaces/IConfidencePool.sol";
import {PoolStates} from "src/libraries/PoolStates.sol";
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {BaseConfidencePoolTest} from "test/helpers/BaseConfidencePoolTest.sol";
import {console2} from "forge-std/Test.sol";
contract TempPartialClaimDustStrandTest is BaseConfidencePoolTest {
function testPartialClaimLeavesUnreachableDustReservedForever() external {
_stake(alice, 100 * ONE);
_stake(bob, 100 * ONE);
_contributeBonus(carol, 51);
_passThroughUnderAttack();
attackRegistry.setAgreementState(IAttackRegistry.ContractState.PRODUCTION);
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.SURVIVED, false, address(0));
uint256 aliceBalanceBefore = token.balanceOf(alice);
vm.prank(alice);
pool.claimSurvived();
uint256 alicePayout = token.balanceOf(alice) - aliceBalanceBefore;
console2.log("alice rewards:", alicePayout);
uint256 bobBalanceBefore = token.balanceOf(bob);
vm.prank(bob);
pool.claimSurvived();
uint256 bobPayout = token.balanceOf(bob) - bobBalanceBefore;
console2.log("bob rewards:", bobPayout);
uint256 poolRemainingBalance = token.balanceOf(address(pool));
console2.log("Pool Remaining Balance:", poolRemainingBalance);
}
}
```
</details>
Recommended Mitigation
**Recommended Mitigation:**
when ever every `staker` claimed, meaning the `totalEligibleStake` is zero then owner can sweep.
```diff
+ function sweepDust() public {
+ if (outcome == PoolStates.Outcome.UNRESOLVED) revert();
+ if (totalEligibleStake != 0) revert();
+ stakeToken.safeTransfer(recoveryAddress, amount);
+ }
```