### Summary
Integer division in `closePot` truncates calculations for both the manager's cut and individual claimant cuts. The leftover dust from these divisions has no withdrawal path and becomes permanently stuck in the contract balance.
### Vulnerability Detail
Inside `Pot.sol`, the division math inside `closePot` truncates remainder values down:
```solidity
uint256 managerCut = remainingRewards / managerCutPercent;
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
```
Because Solidity does not support fractional numbers, any division remainder (dust) stays within the contract. Since the function sets `remainingRewards = 0` at completion and marks the pot as closed, this dust accumulates over time with no administrative fallback mechanism to extract it.
### Impact
Locked funds. Small amounts of dust accumulate across multiple pots. While individually negligible per contest, the trapped capital across many contests becomes permanently lost.
### Affected Code
* `Pot.sol`
### Proof of Concept
Add the following test case to your test suite to verify the truncation accumulation:
```solidity
function test_L03_PrecisionLossInClosePot() public {
address[] memory players = new address[](7);
uint256[] memory rewards = new uint256[](7);
for (uint256 i = 0; i < 7; i++) {
players[i] = address(uint160(i + 1));
rewards[i] = 100e18;
}
for (uint256 i = 0; i < 4; i++) {
vm.prank(players[i]);
pot.claimCut();
}
vm.warp(block.timestamp + 91 days);
uint256 potBalanceBefore = weth.balanceOf(address(pot));
pot.closePot();
uint256 expectedDistributed = 30 + (38 * 4);
uint256 actualStuck = potBalanceBefore - expectedDistributed;
assertGt(actualStuck, 0);
assertEq(weth.balanceOf(address(pot)), actualStuck);
}
```
### Tools Used
* Manual Review
### Recommendations
Sweep any remaining contract token balance to the first claimant or back to the manager after the main distribution loop completes:
```solidity
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) revert Pot__StillOpenForClaim();
if (remainingRewards > 0) {
uint256 managerCut = remainingRewards / managerCutPercent;
i_token.transfer(msg.sender, managerCut);
if (claimants.length > 0) {
uint256 claimantCut = (remainingRewards - managerCut) / claimants.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
uint256 dust = i_token.balanceOf(address(this));
if (dust > 0 && claimants.length > 0) {
_transferReward(claimants[0], dust);
}
}
remainingRewards = 0;
closed = true;
}
```
use in the codebase with @> marks to highlight the relevant section