Skip to content
Request an audit

LP pool cap may be exceeded on drawing settlement

TitleLP pool cap may be exceeded on drawing settlement
Reward$9542, 2 dups
ContestMegapot - 3 November 2025 on Code4rena
Authormontecristo
ContextCap 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
Codebase

Message on Telegram All 62 posts