Mystery Box

First Flight #25
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Valid

Gas Griefing Vulnerability on `claimAllRewards` Function

Summary

The claimAllRewards function in the MysteryBox contract was identified as vulnerable to gas griefing due to the potential for users to accumulate a large number of rewards. This could cause the function to exceed the gas limit when attempting to claim all rewards at once, leading to failed transactions. Tests confirmed that without mitigation, this vulnerability could prevent users from claiming their accumulated rewards, especially in scenarios with numerous entries. This report outlines the risks associated with gas griefing and demonstrates how to handle large data sets in smart contracts efficiently.

Vulnerability Details

The claimAllRewards function allowed users to accumulate and claim multiple rewards simultaneously. However, as the number of accumulated rewards grew, the gas required to process this function also increased, potentially exceeding the block gas limit. This would result in failed transactions, effectively trapping the rewards and preventing users from claiming them.

Details:

  • Accumulation of Rewards: Users could open multiple mystery boxes and accumulate a large number of rewards over time.

  • Impact: When attempting to claim all accumulated rewards in one transaction, the gas required to process the loop through all rewards could exceed the block gas limit, causing the transaction to revert.

Code Analysis

Original claimAllRewards Function:

function claimAllRewards() public {
uint256 totalValue = 0;
for (uint256 i = 0; i < rewardsOwned[msg.sender].length; i++) {
totalValue += rewardsOwned[msg.sender][i].value;
}
require(totalValue > 0, "No rewards to claim");
(bool success, ) = payable(msg.sender).call{value: totalValue}("");
require(success, "Transfer failed");
delete rewardsOwned[msg.sender];
}

Key Issues:

  • The for loop iterates over the entire rewardsOwned array, which could become extremely gas-intensive as the array grows.

  • There was no mechanism to batch the claims or limit the number of iterations, making it easy to exceed the block gas limit.

Mitigated Version with Batched Processing

function claimRewardsBatch(uint256 batchSize) public {
uint256 totalValue = 0;
uint256 rewardsLength = rewardsOwned[msg.sender].length;
require(rewardsLength > 0, "No rewards to claim");
require(batchSize > 0, "Batch size must be greater than 0");
uint256 iterations = rewardsLength < batchSize ? rewardsLength : batchSize;
for (uint256 i = 0; i < iterations; i++) {
totalValue += rewardsOwned[msg.sender][i].value;
delete rewardsOwned[msg.sender][i];
}
// Compact the array
for (uint256 j = 0; j < iterations; j++) {
rewardsOwned[msg.sender][j] = rewardsOwned[msg.sender][rewardsLength - j - 1];
rewardsOwned[msg.sender].pop();
}
require(totalValue > 0, "No rewards to claim");
(bool success, ) = payable(msg.sender).call{value: totalValue}("");
require(success, "Transfer failed");
}

Key Strength:

  • The batched processing mechanism prevents the function from attempting to process too many entries at once, avoiding potential gas griefing attacks and ensuring successful claims even with large numbers of rewards.

Proof of Concept (PoC)

  1. Before Mitigation: A test was conducted to simulate the gas griefing vulnerability in the claimAllRewards function. A user accumulated 2,000 rewards, and when attempting to claim all rewards at once, the transaction failed due to exceeding the gas limit.

function testGasGriefing() public {
vm.startPrank(user);
uint256 rewardCount = 2000; // Number of rewards to accumulate
// Simulate reward accumulation
for (uint256 i = 0; i < rewardCount; i++) {
mysteryBox.setTestMode(true, 99); // Force a "Gold Coin" reward
mysteryBox.buyBox{value: 0.1 ether}();
mysteryBox.openBox();
}
// Attempt to claim all rewards at once
try mysteryBox.claimAllRewards() {
emit log("Claim all rewards successful - No gas griefing detected");
} catch {
emit log("Gas griefing detected - Failed to claim all rewards");
}
vm.stopPrank();
}

Results:

  • Before Mitigation: The test confirmed that attempting to claim all 2,000 rewards in one transaction failed due to exceeding the gas limit, demonstrating the gas griefing vulnerability.

  1. After Mitigation: The test was repeated using the batched processing approach, allowing the user to claim rewards in manageable chunks without exceeding the gas limit.

function testGasGriefingMitigation() public {
vm.startPrank(user);
uint256 rewardCount = 2000; // Number of rewards to accumulate
// Simulate reward accumulation
for (uint256 i = 0; i < rewardCount; i++) {
mysteryBox.setTestMode(true, 99); // Force a "Gold Coin" reward
mysteryBox.buyBox{value: 0.1 ether}();
mysteryBox.openBox();
}
// Claim rewards in batches of 100
uint256 totalClaimed = 0;
while (mysteryBox.getRewards().length > 0) {
mysteryBox.claimRewardsBatch(100);
totalClaimed += 100;
emit log_named_uint("Successfully claimed batch of rewards. Total claimed:", totalClaimed);
}
vm.stopPrank();
}

Results:

  • The test successfully demonstrated that the batched processing allowed all rewards to be claimed without exceeding the gas limit.

Gas Costs Comparison Before and After Mitigation

The test GasGriefingTest.t.sol was used to compare the gas costs for claiming all rewards at once versus using the batched approach:

Before Mitigation:

  • Attempting to claim 2,000 rewards: Transaction failed due to exceeding gas limit.

After Mitigation:

  • Batched Claiming (Batch size = 100):

    • Gas used for each batch: Significantly reduced and consistent across batches.

Impact

  • Transaction Failure Risk: The original implementation could cause failed transactions due to gas limits, preventing users from claiming their rewards.

  • Effective Mitigation: The batched processing approach reduced gas costs and allowed users to successfully claim their rewards without hitting the gas limit.

Tools Used

  • Manual code analysis

  • Foundry for testing gas griefing scenarios

Recommendations

  1. Implement Batched Processing: Use batched processing for functions that handle large data sets to avoid exceeding gas limits.

  2. Monitor Gas Usage: Regularly analyze the gas costs of functions that handle user-owned data to identify potential vulnerabilities early.

  3. Provide User Guidance: Inform users of the need to claim rewards in batches when dealing with a large number of entries.

Conclusion

The claimAllRewards function was vulnerable to gas griefing due to the lack of a batching mechanism, which could lead to failed transactions when claiming a large number of rewards. By implementing batched processing, this vulnerability was successfully mitigated, ensuring efficient gas usage and preventing the potential loss of rewards due to gas limits. This serves as a reminder of the importance of managing gas usage in functions that process large arrays or data sets in Ethereum smart contracts.

Updates

Appeal created

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Gas Limit Exhaustion in `claimAllRewards` Function

Support

FAQs

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

Give us feedback!