Status DataClose notification

Majority Games Disclosed Report

Reward Funds Locked Due to Number of Winners Mismatch in ProportionalToXPReward

Created date
Aug 29 2025

Target

hidden

Vulnerability Details

The mismatch between numberOfWinners set via setNumberOfWinners and the actual winners array passed during assertResults causes claimRewards() → getReward() in ProportionalToXPReward to always revert. This results in a lock of reward funds, as rewards can never be claimed once the game concludes, making it a critical vulnerability

Root cause:

  • setNumberOfWinners(sessionId, n) sets the expected number of winners.
  • During assertResults(sessionId, winners), the protocol does not enforce that the passed winners array length equals the stored numberOfWinners[sessionId].
  • Later in reward claiming:
require(
    numberOfWinners[sessionId] == winners.length, 
    NumberOfWinnersMismatch(sessionId, winners.length)
);

Additionally, since setNumberOfWinners is restricted to the Created state, the mismatch cannot be corrected once the game has started.

Code References: https://github.com/hackenproof-public/engage-protocol/blob/8476672096d591459dff56517956668f1f94b133/src/reward/ProportionalToXPReward.sol#L88 https://github.com/hackenproof-public/engage-protocol/blob/8476672096d591459dff56517956668f1f94b133/src/session/DefaultSession.sol#L136 https://github.com/hackenproof-public/engage-protocol/blob/8476672096d591459dff56517956668f1f94b133/src/SessionManager.sol#L540 https://github.com/hackenproof-public/engage-protocol/blob/8476672096d591459dff56517956668f1f94b133/src/reward/ProportionalToXPReward.sol#L68

Impact: Lock of Funds as claimRewards() will always reverts

Mitigation: Consider these options accordingly

// Option 1: Enforce consistency during results assertion

require(
    numberOfWinners[sessionId] == winners.length,
    NumberOfWinnersMismatch(sessionId, winners.length)
);

Or

// Option 2: Allow updating until results are finalized

require(
    state == SessionState.Created || state == SessionState.Started, 
    "Invalid state"
);

Validation steps

  1. Game creator calls setNumberOfWinners(sessionId, 2).
  2. Later, assertResults(sessionId, [addr1]) is executed with a single winner
  3. numberOfWinners[sessionId] = 2, but winners.length = 1 (or vice versa)
  4. When winners attempt claimRewards(), execution reverts due to NumberOfWinnersMismatch.
  5. Because the game is already in the Concluded state, neither cancellation nor updating numberOfWinners is possible. As a result, all reward tokens remain permanently locked → Reward tokens locked.

Coded POC attached. Refer @audit comments Update MajorityBaseTest.t.sol Create new file FullGameTestICX.t.sol

cmd: forge test --mt "test_FullGameFlow1" -vvvv

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Critical
Bounty$669
Visibilitypartially
VulnerabilityDoS
Participants
hidden