<!-- canonical: https://0xsimao.com/findings/pooltogether-the-prize-layer-for-defi-estimated-draws-tiered-rounding -->

# Estimated prize draws in TieredLiquidityDistributor are off due to rounding down when calculating the sum, leading to incorrect prizes

Medium · Sherlock · Raffle · 16th May 2024

Finding M-10 of the PoolTogether Prize Layer competition.

- Protocol: https://audits.sherlock.xyz/contests/225
- Report: /reports/pooltogether-the-prize-layer-for-defi
- Codebase: https://github.com/0xsimao/2024-05-pooltogether/tree/1aa1b8c028b659585e4c7a6b9b652fb075f86db3
- Source: https://github.com/sherlock-audit/2024-05-pooltogether-judging/issues/95

---

## Summary

Estimated prize draws in `TieredLiquidityDistributor` are off due to rounding down when calculating the expected prize count on each tier, leading to an incorrect next number of tiers and distribution of rewards.

## Vulnerability Detail

`ESTIMATED_PRIZES_PER_DRAW_FOR_5_TIERS` and the other tiers are precomputed initially in `TieredLiquidityDistributor::_sumTierPrizeCounts()`. In [here](https://github.com/sherlock-audit/2024-05-pooltogether/blob/main/pt-v5-prize-pool/src/abstract/TieredLiquidityDistributor.sol#L574), it goes through all tiers and calculates the expected prize count per draw for each tier. However, when doing this [calculation](https://github.com/sherlock-audit/2024-05-pooltogether/blob/main/pt-v5-prize-pool/src/libraries/TierCalculationLib.sol#L113-L115) in `TierCalculationLib.tierPrizeCountPerDraw()`, `uint32(uint256(unwrap(sd(int256(prizeCount(_tier))).mul(_odds))));`, it rounds down the prize count of each tier. This will lead to an incorrect prize count calculation, for example:

```solidity
grandPrizePeriodDraws == 8 days
numTiers == 5
prizeCount(tier 0) == 4**0 * 1 / 8 == 1 * 1 / 8 == 0.125 = 0
prizeCount(tier 1) == 4**1 * (1 / 8)^sqrt((1 + 1 - 3) / (1 - 3)) == 4 * 0.2298364718 ≃ 0.92 = 0
prizeCount(tier 2) == 4**2 * 1 == 16
prizeCount(tier 3) == 4**3 * 1 == 64
total = 80
```
However, if we multiply the prize counts by a constant and then divide the sum in the end, the total count would be 81 instead, getting an error of 1 / 81 ≃ 1.12 %

## Impact

The estimated prize count will be off, which affects the calculation of the next number of tiers in [PrizePool::computeNextNumberOfTiers()](https://github.com/sherlock-audit/2024-05-pooltogether/blob/main/pt-v5-prize-pool/src/PrizePool.sol#L472). This modifies the whole rewards distribution for the next draw.

## Code Snippet

https://github.com/sherlock-audit/2024-05-pooltogether/blob/main/pt-v5-prize-pool/src/abstract/TieredLiquidityDistributor.sol#L574
https://github.com/sherlock-audit/2024-05-pooltogether/blob/main/pt-v5-prize-pool/src/libraries/TierCalculationLib.sol#L113-L115

## Tool used

Manual Review

Vscode

## Recommendation

Add some precision to the calculations by multiplying, for example, by `1e5` each count and then dividing the sum by `1e5`. `uint32(uint256(unwrap(sd(int256(prizeCount(_tier)*1e5)).mul(_odds))));` and `return prizeCount / 1e5;`.

---

Related findings:

- [Users will lock raffle prizes on the `WinnablesPrizeManager` contract by calling `WinnablesTicketManager::propagateRaffleWinner` with wrong CCIP inputs](https://0xsimao.com/findings/winnables-raffles-lock-propagate-ccip-inputs): Winnables Raffles
- [`Voter::finalize()` incorrect rewards distribution due to transfering WETH before calling `distributor::setTokensPerInterval()`](https://0xsimao.com/findings/bmx-voter-rewards-transfering-weth): BMX
