MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Sum of individual rewards not validated against totalRewards – Risk of underfunding and stuck funds

[H-01] Sum of individual rewards not validated against totalRewards – Risk of underfunding and stuck funds

Severity: High Location: Pot.sol, constructor

Description

The Pot constructor accepts an array of rewards and a separate 'totalRewards' parameter but does not verify that the sum of all elements in rewards equals 'totalRewards'.

If the provided rewards sum to less than 'totalRewards', the contract will expect more funds than are actually claimable by players. When funding the exact 'totalRewards' (as done by 'ContestManager'), the excess funds ('totalRewards' - actual sum) will remain unclaimable forever:

  • Players can only claim their individual reward (sum totalRewards, then early claimants can drain, later revert on transfer (if funded exactly), or remainingRewards underflow (currently no check, but subtraction would wrap around in older solidity, but ^0.8 safe).

But main risk is sum < totalRewards, common mistake.

Impact

  • Permanent loss of the excess tokens sent to the contract.

  • Manager receives only a small portion of the unclaimed excess on close.

  • The bulk of excess is lost unless close logic is fixed (but it's not).

Proof of Concept

here is the runnable poc that shows how the above described issue can be exploited

pragma solidity ^0.8.20;
import {Test} from "../lib/forge-std/src/Test.sol";
import {Pot} from "../src/Pot.sol";
import {ERC20Mock} from "./ERC20Mock.sol";
contract PotTest is Test {
Pot pot;
ERC20Mock token;
address[] players;
uint256[] rewards;
address owner = address(0x1);
address player1 = address(0x2);
address player2 = address(0x3);
function setUp() public {
token = new ERC20Mock("mck", "MCk", owner, 1000 ether);
token.mint(address(this), 1000 ether);
players = new address[](2);
players[0] = player1;
players[1] = player2;
rewards = new uint256[](2);
rewards[0] = 40 ether;
rewards[1] = 50 ether; // sum = 90 ether, but totalRewards = 100 ether
}
function test_SumMismatchOver() public {
rewards[0] = 60 ether;
rewards[1] = 50 ether; // sum = 110 ether, but totalRewards = 100 ether
pot = new Pot(players, rewards, token, 100 ether);
token.transfer(address(pot), 100 ether); // Fund exactly totalRewards
vm.prank(player1);
pot.claimCut(); // Claims 60, remainingRewards -=60 → 40 ether left
vm.prank(player2);
// This succeeds in subtraction (remainingRewards -=50 → reverts on underflow in ^0.8)
vm.expectRevert(); // Underflow revert
pot.claimCut();
// Or if no underflow check (pre-0.8), wraps to huge number, but transfer fails anyway
assertEq(token.balanceOf(address(pot)), 40 ether); // Player2 can't claim
}
}

Recommended mitigation

In the constructor, compute the sum of rewards and require it equals totalRewards. Revert with a descriptive error if not.

solidity

uint256 sum;
for (uint256 i = 0; i < rewards.length; i++) {
sum += rewards[i];
}
require(sum == totalRewards, "Rewards sum must equal totalRewards");
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!