The getRewards function is responsible for calculating the total rewards available for distribution to winners by deducting protocol, creator, and referrer fees from the pool’s totalCollectedAmount. However, the current implementation applies the REFERRER_FEE deduction to the entire totalCollectedAmount, which includes both entry fees (from players) and sponsor contributions.
In reality, referrer rewards are only accumulated on entry fees, not on sponsor amounts. This discrepancy leads to an over-deduction whenever sponsor contributions are present.
/**
* @notice Calculates the total rewards available for distribution to winners
* @param sessionId The ID of the session
* @return The amount of tokens available for rewards after deducting fees
*/
function getRewards(uint256 sessionId) public view returns (uint256) {
GamePool storage pool = gamePools[sessionId];
return pool.totalCollectedAmount * (BASIS_POINTS - (pool.creatorFee + pool.protocolFee + REFERRER_FEE))
/ BASIS_POINTS;
}
REFERRER_FEE is applied on totalCollectedAmount, while actual payout logic only considers entryFees.
This creates a mismatch: referrer fees are deducted from sponsor funds, but never actually paid out, leaving a portion of funds locked in the contract.
When sponsor contributions exist, part of the funds become permanently unclaimable.
2% of any amount added by the sponsors are always stuck in the contract.
Example:
Entry fees = 1000e18
Sponsors = 1000e18
Total = 2000e18
Referrer payouts = 1000e18 * 2% = 20e18
But getRewards deducts 2000e18 * 2% = 40e18
Mismatch = 20e18 tokens stuck in contract.
Copy test into test/SessionManagerEndGame.t.sol
function test_referral_funds_stuck() public {
_createGame(sessionManager, promptHashes, promptStrategies, sessionStrategy, rewardStrategy, usdc);
_addContestants();
address sponsor1 = makeAddr("Sponsor1");
address sponsor2 = makeAddr("Sponsor2");
TestUSDC(usdc).mint(sponsor1, 100 ether);
TestUSDC(usdc).mint(sponsor2, 100 ether);
// First sponsor
vm.startPrank(sponsor1);
TestUSDC(usdc).approve(address(sessionManager), 100 ether);
sessionManager.sponsorGame(1, 100 ether);
vm.stopPrank();
// Second sponsor
vm.startPrank(sponsor2);
TestUSDC(usdc).approve(address(sessionManager), 100 ether);
sessionManager.sponsorGame(1, 100 ether);
vm.stopPrank();
vm.prank(address(this));
_startGame();
_revealQuestion();
_warpToEndTime();
sessionManager.endGame(1);
_concludeGame();
sessionManager.distributeFees(1);
for (uint256 i = 0; i < 3; i++) {
vm.startPrank(contestants[i]);
sessionManager.claimRewards(1, i);
vm.stopPrank();
}
vm.startPrank(address(this));
sessionManager.claimReferralRewards(Solarray.uint256s(1));
vm.stopPrank();
assertGt(TestUSDC(usdc).balanceOf(address(sessionManager)), 0);
assertEq(TestUSDC(usdc).balanceOf(address(sessionManager)), 4e18); // 4 USDC stuck in contract
}