MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: high
Valid

closePot Loops Over claimants Array — Attacker Can Bloat Array to Block Gas Limit, Permanently Locking All Funds

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

# [M-#] `closePot` Loops Over `claimants` Array — Attacker Can Bloat Array to Block Gas Limit, Permanently Locking All Funds
## Summary
The `closePot` cleanup routine relies on an unbounded loop to parse the `claimants` collection storage array and process payouts in a single transaction frame. Because the size of this array scales up continuously with each successive user claim, an attacker can create multiple sybil accounts to inflate the length of the list. This drives the transaction gas consumption beyond the hard block limits of the network, breaking the closure path and permanently trapping all remaining contract funds.
## Vulnerability Details
In `Pot.sol`, the administrative `closePot` function iterates through every recorded history item inside the `claimants` registry after the mandatory 90-day contest duration window expires:
```solidity
for (uint256 i = 0; i < claimants.length; i++) {
// Iterates over all claimants to transfer tokens
}
```
This structural architecture introduces a classical denial-of-service vector due to unconstrained execution scaling. A malicious user can programmatically generate hundreds of dummy addresses and execute interactions that repeatedly push new entries into the `claimants` array.
When a manager attempts to run the `closePot` cleanup task, the EVM must evaluate every single array element inside that single transaction context. If the list is sufficiently bloated, the required gas to complete the loop exceeds the block limit, causing the transaction to fail and preventing the execution of the cleanup method.
## Impact
**Medium.** The critical protocol cleanup routine can be rendered permanently inoperable. This causes an irreversible denial of service that traps all leftover rewards, uncollected assets, and the manager's 10% protocol cut inside the contract balance permanently.
## Proof of Concept
Add the following test case to your suite to confirm the array bloat gas exhaustion vulnerability:
```solidity
function test_ClaimantsArrayBloat_DOS_ClosePot() public {
// 1. Attacker inflates the claimants storage array using 500 unique sybil accounts
for (uint256 i = 0; i < 500; i++) {
address claimant = address(uint160(uint256(keccak256(abi.encode(i)))));
vm.prank(claimant);
pot.claimCut(); // Sequentially expands the claimants list length
}
// 2. Fast-forward past the required 90-day lock period
vm.warp(block.timestamp + 91 days);
// 3. The closePot cleanup transaction reverts due to block gas limit exhaustion
vm.expectRevert();
pot.closePot();
}
```
## Tools Used
* Manual Code Review
## Recommendations
Replace the push distribution mechanism with an independent pull-based distribution model. This lets users withdraw their own bonuses in separate transactions, isolating the gas costs to each claimant:
```solidity
// @audit-issue Remove the unbounded loop from closePot and switch to a pull pattern
function claimBonus() external {
require(closed, "Contest pot has not been closed by management yet");
require(!bonusClaimed[msg.sender], "Bonus allocation already claimed");
bonusClaimed[msg.sender] = true;
uint256 bonus = calculateBonus(msg.sender);
i_token.transfer(msg.sender, bonus);
}
```
If executing loops remains a core design requirement, implement incremental index checkpoints to allow processing the array over multiple distinct, gas-bounded execution batches.
// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Recommended Mitigation

- remove this code
+ add this code
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-04] Gas Limit DoS via large amount of claimants

## Description The `Pot.sol` contract contains a vulnerability that can lead to a Denial of Service (DoS) attack. This issue arises from the inefficient handling of claimants in the `closePot` function, where iterating over a large number of claimants can cause the transaction to run out of gas, thereby preventing the contract from executing as intended. ## Vulnerability Details Affected code - <https://github.com/Cyfrin/2024-08-MyCut/blob/946231db0fe717039429a11706717be568d03b54/src/Pot.sol#L58> The vulnerability is located in the `closePot` function of the Pot contract, specifically at the loop iterating over the claimants array: ```javascript function closePot() external onlyOwner { ... if (remainingRewards > 0) { ... @> for (uint256 i = 0; i < claimants.length; i++) { _transferReward(claimants[i], claimantCut); } } } ``` The `closePot` function is designed to distribute remaining rewards to claimants after a contest ends. However, if the number of claimants is extremly large, the loop iterating over the claimants array can consume a significant amount of gas. This can lead to a situation where the transaction exceeds the gas limit and fails, effectively making it impossible to close the pot and distribute the rewards. ## Exploit 1. Attacker initiates a big contest with a lot of players 2. People claim the cut 3. Owner closes the large pot that will be very costly ```javascript function testGasCostForClosingPotWithManyClaimants() public mintAndApproveTokens { // Generate 2000 players address[] memory players2000 = new address[](2000); uint256[] memory rewards2000 = new uint256[](2000); for (uint256 i = 0; i < 2000; i++) { players2000[i] = address(uint160(i + 1)); rewards2000[i] = 1 ether; } // Create a contest with 2000 players vm.startPrank(user); contest = ContestManager(conMan).createContest(players2000, rewards2000, IERC20(ERC20Mock(weth)), 2000 ether); ContestManager(conMan).fundContest(0); vm.stopPrank(); // Allow 1500 players to claim their cut for (uint256 i = 0; i < 1500; i++) { vm.startPrank(players2000[i]); Pot(contest).claimCut(); vm.stopPrank(); } // Fast forward time to allow closing the pot vm.warp(91 days); // Record gas usage for closing the pot vm.startPrank(user); uint256 gasBeforeClose = gasleft(); ContestManager(conMan).closeContest(contest); uint256 gasUsedClose = gasBeforeClose - gasleft(); vm.stopPrank(); console.log("Gas used for closing pot with 1500 claimants:", gasUsedClose); } ``` ```Solidity Gas used for closing pot with 1500 claimants: 6425853 ``` ## Impact The primary impact of this vulnerability is a Denial of Service (DoS) attack vector. An attacker (or even normal usage with a large number of claimants) can cause the `closePot` function to fail due to excessive gas consumption. This prevents the distribution of remaining rewards and the execution of any subsequent logic in the function, potentially locking funds in the contract indefinitely. In the case of smaller pots it would be a gas inefficency to itterate over the state variabel `claimants`. ## Recommendations Gas Optimization: Optimize the loop to reduce gas consumption by using a local variable to itterate over, like in the following example: ```diff - for (uint256 i = 0; i < claimants.length; i++) { - _transferReward(claimants[i], claimantCut); - } + uint256 claimants_length = claimants.length; + ... + for (uint256 i = 0; i < claimants_length; i++) { + _transferReward(claimants[i], claimantCut); + } ``` Batch Processing: Implement batch processing for distributing rewards. This will redesign the protocol functionallity but instead of processing all claimants in a single transaction, allow the function to process a subset of claimants per transaction. This can be achieved by introducing pagination or limiting the number of claimants processed in one call. This could also be fixed if the user would claim their reward after 90 days themselves

Support

FAQs

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

Give us feedback!