Mystery Box

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

Incorrect Deletion of Rewards

Summary

The MysteryBox contract incorrectly deletes elements from the rewardsOwned array using the delete keyword. This practice sets the array element to its default value but does not remove it from the array, leading to "holes" or gaps. These gaps can cause issues when iterating over the array, potentially resulting in incorrect reward calculations, logical errors, and unexpected behavior.

Vulnerability Details

In the transferReward and claimSingleReward functions, the contract uses the delete keyword to remove elements from the rewardsOwned array:

delete rewardsOwned[msg.sender][_index];

Using delete on an array element in Solidity sets that element to its default value but does not:

  • Adjust the array's length.

  • Shift subsequent elements to fill the gap.

This behavior results in an array with the same length but containing empty (default) elements at the deleted indices.

Functions Affected:

  • transferReward

  • claimSingleReward

Impact

  • Inaccurate Reward Distribution: Users may receive incorrect reward amounts due to the inclusion of default (empty) rewards in calculations.

  • Logical Errors: Functions that rely on the array's integrity may behave unexpectedly, potentially leading to security vulnerabilities or application crashes.

  • User Frustration: Users may experience issues when claiming or transferring rewards, leading to a loss of trust in the platform.

  • Potential Exploitation: Malicious users could exploit this behavior to manipulate reward calculations.

Tools Used

  • Manual Code Review: Examined the contract's code to identify improper array manipulation practices.

Recommendations

To correctly remove an element from an array in Solidity, replace the element to be deleted with the last element of the array and then reduce the array's length using the pop() method. This approach maintains a contiguous array without gaps and ensures the array length accurately reflects the number of elements.

For transferReward:

function transferReward(address _to, uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
// Transfer the reward to the recipient
rewardsOwned[_to].push(rewardsOwned[msg.sender][_index]);
// Replace the deleted element with the last element
rewardsOwned[msg.sender][_index] = rewardsOwned[msg.sender][rewardsOwned[msg.sender].length - 1];
// Remove the last element
rewardsOwned[msg.sender].pop();
}

For claimSingleReward:

function claimSingleReward(uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
uint256 value = rewardsOwned[msg.sender][_index].value;
require(value > 0, "No reward to claim");
// Remove the reward before external call to prevent reentrancy
// Swap and pop
rewardsOwned[msg.sender][_index] = rewardsOwned[msg.sender][rewardsOwned[msg.sender].length - 1];
rewardsOwned[msg.sender].pop();
// Transfer the reward value to the user
(bool success,) = payable(msg.sender).call{value: value}("");
require(success, "Transfer failed");
}
Updates

Appeal created

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

A user can poison the `rewardsOwned` of another user via `transferReward` of an empty reward index

Support

FAQs

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

Give us feedback!