Root + Impact
Description
-
Describe the normal behavior in one or more sentences
-
The normal behavior should be: validate all inputs before deployment to ensure the contest can function correctly.
-
Explain the specific issue or problem in one or more sentences
-
The createContest() function does not validate critical input parameters before deploying a Pot contract.
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
return address(pot);
}
Risk
Likelihood:
Impact:
-
Impact 1
-
Array length mismatch:
-
Impact 2
Proof of Concept
function testArrayLengthMismatch() public {
address[] memory players = new address[](5);
uint256[] memory rewards = new uint256[](3);
for (uint i = 0; i < 5; i++) {
players[i] = address(uint160(i + 1));
}
for (uint i = 0; i < 3; i++) {
rewards[i] = 1000;
}
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 3000);
assertEq(Pot(pot).checkCut(players[3]), 0);
assertEq(Pot(pot).checkCut(players[4]), 0);
}
function testRewardsSumMismatch() public {
address[] memory players = new address[](3);
uint256[] memory rewards = new uint256[](3);
players[0] = alice;
players[1] = bob;
players[2] = charlie;
rewards[0] = 100;
rewards[1] = 200;
rewards[2] = 300;
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 1000);
token.transfer(pot, 1000);
vm.prank(alice);
Pot(pot).claimCut();
vm.prank(bob);
Pot(pot).claimCut();
vm.prank(charlie);
Pot(pot).claimCut();
assertEq(token.balanceOf(pot), 400);
}
function testDuplicatePlayersLockFunds() public {
address[] memory players = new address[](3);
uint256[] memory rewards = new uint256[](3);
players[0] = alice;
players[1] = bob;
players[2] = alice;
rewards[0] = 1000;
rewards[1] = 2000;
rewards[2] = 3000;
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 6000);
token.transfer(pot, 6000);
assertEq(Pot(pot).checkCut(alice), 3000);
vm.prank(alice);
Pot(pot).claimCut();
vm.prank(bob);
Pot(pot).claimCut();
assertEq(token.balanceOf(pot), 1000);
}
function testEmptyArraysLockAllFunds() public {
address[] memory players = new address[](0);
uint256[] memory rewards = new uint256[](0);
vm.prank(owner);
address pot = contestManager.createContest(players, rewards, token, 10000);
token.transfer(pot, 10000);
assertEq(token.balanceOf(pot), 10000);
}
Recommended Mitigation
- remove this code
+ add this code// ContestManager.sol
function createContest(
address[] memory players,
uint256[] memory rewards,
IERC20 token,
uint256 totalRewards
) public onlyOwner returns (address) {
+ // Validate arrays are non-empty
+ require(players.length > 0, "No players");
+ require(rewards.length > 0, "No rewards");
+
+ // Validate arrays have matching lengths
+ require(
+ players.length == rewards.length,
+ "Array length mismatch"
+ );
+
+ // Validate token address
+ require(address(token) != address(0), "Invalid token");
+
+ // Validate rewards sum equals totalRewards
+ uint256 rewardsSum = 0;
+ for (uint256 i = 0; i < rewards.length; i++) {
+ require(rewards[i] > 0, "Zero reward not allowed");
+ rewardsSum += rewards[i];
+ }
+ require(
+ rewardsSum == totalRewards,
+ "Rewards sum does not match total"
+ );
+
+ // Check for duplicate players
+ for (uint256 i = 0; i < players.length; i++) {
+ require(players[i] != address(0), "Invalid player address");
+
+ // Check against all previous players
+ for (uint256 j = 0; j < i; j++) {
+ require(
+ players[i] != players[j],
+ "Duplicate player address"
+ );
+ }
+ }
// Create a new Pot contract
Pot pot = new Pot(players, rewards, token, totalRewards);
contests.push(address(pot));
contestToTotalRewards[address(pot)] = totalRewards;
+
+ emit ContestCreated(address(pot), players.length, totalRewards);
return address(pot);
}