# LP pool cap may be exceeded on drawing settlement

Bug Deep Dive #33 · 18 January 2026

Bug Deep Dives · [The Contest Academy](https://0xsimao.com/the-contest-academy) · 0xSimao

---

| Title | LP pool cap may be exceeded on drawing settlement |
| Reward | $9542, 2 dups |
| Contest | Megapot - 3 November 2025 on Code4rena |
| Author | montecristo |
| Context | Cap exceeded |

Jackpot enforces pool cap as the following:

```solidity
function _calculateLpPoolCap(uint256 _normalBallMax) internal view returns (uint256) {
    // We use MAX_BIT_VECTOR_SIZE because that's the max number that can be packed in a uint256 bit vector
    uint256 maxAllowableTickets = Combinations.choose(_normalBallMax, NORMAL_BALL_COUNT) * (MAX_BIT_VECTOR_SIZE - _normalBallMax);
    uint256 maxPrizePool = maxAllowableTickets * ticketPrice * (PRECISE_UNIT - lpEdgeTarget) / PRECISE_UNIT;

    // We need to make sure that the lpPoolCap is not greater than the governance pool cap
    return Math.min(maxPrizePool * PRECISE_UNIT / (PRECISE_UNIT - reserveRatio), governancePoolCap);
}
```

This is to ensure the following:

- bonusBallMax + normalBallMax <= MAX_BIT_VECTOR_SIZE
- Pool cap does not exceed governance pool cap

Otherwise `TicketComboTracker` cannot store a purchased ticket at the maximum bonus ball, because the following reverts with an overflow:

```solidity
ticketNumbers = set |= 1 << (_bonusball + _tracker.normalMax);
```

However, LP pool cap can be exceeded on drawing settlement, because new LP value calculation does not enforce the same pool cap logic:

```solidity
function processDrawingSettlement(
    uint256 _drawingId,
    uint256 _lpEarnings,
    uint256 _userWinnings,
    uint256 _protocolFeeAmount
) external onlyJackpot() returns (uint256 newLPValue, uint256 newAccumulator) {
    LPDrawingState storage currentLP = lpDrawingState[_drawingId];

    uint256 postDrawLpValue = currentLP.lpPoolTotal + _lpEarnings - _userWinnings - _protocolFeeAmount;

    // ... (other logic between lines 379-390 was omitted in your snippet)

    newLPValue = postDrawLpValue + currentLP.pendingDeposits - withdrawalsInUSDC;
}
```

Since the LP value can grow by up to `lpEdgeTarget = 30%` on every draw with no jackpot winner, the governance cap or the calculated limit can be exceeded whenever the previous total pool sat just below it.

**Impact**

Important invariants can be broken on settlement drawing:

- **Pool Cap Compliance**: Total pool never exceeds governance limits
- Pool cap enforcement: `lpPoolTotal + pendingDeposits <= governancePoolCap`
- Is the bit-packing logic sound? Are there boundary errors between the lower bits, where the normal balls sit, and the higher bits, where the bonus ball must stay below `255 - normalBallMax`?

In particular, once the new pool cap exceeds the calculated safe limit, `bonusBallMax` can exceed `255 - normalBallMax`.

That denies service to `normalBallMax` betting, and ultimately makes the betting unfair.

**Alpha:** read every stated invariant and confirm none can be broken. Here, a per-variable analysis is enough: check each update and whether it is capped.

**Conclusion**

This finding would earn you **\$9542**, and needed nothing more than checking whether the stated invariant could break.

[**Full Report**](https://code4rena.com/reports/2025-11-megapot#h-03-lp-pool-cap-may-be-exceeded-on-drawing-settlement)\
[**Codebase**](https://github.com/code-423n4/2025-11-megapot/tree/f0a7297d59c376e38b287b2c56740617dbbfbdc7)

---

Older: [Factory deploy reverts instead of returning address when account already exists](https://0xsimao.com/the-contest-academy/bug-deep-dive-32)
