Mystery Box

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

Inefficient and Potentially Confusing Reward Deletion Mechanism

Summary

The transferReward function deletes a specific reward by index without reorganizing the rewards array, leading to potential gaps and zeroed entries in the rewardsOwned mapping. This can cause inefficiencies and confusion when managing or displaying rewards to users.

Vulnerability Details

In the MysteryBox.sol contract, the transferReward function deletes a reward at a specific index:

function transferReward(address _to, uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
rewardsOwned[_to].push(rewardsOwned[msg.sender][_index]);
delete rewardsOwned[msg.sender][_index];
}

Using delete on a specific index sets the Reward struct at that position to its default values ("" for name and 0 for value) but does not remove the element from the array. This results in empty or zeroed entries within the rewardsOwned array, which can lead to inefficiencies and unintended behavior when iterating over rewards.

Impact

this can lead to confusion for users by introducing empty rewards in the array. It may also cause increased gas consumption due to handling unnecessary zeroed entries, particularly if users accumulate a large number of rewards with gaps.

PoC

After transferring a reward, the rewardsOwned array contains an empty entry:

// Before transfer
rewardsOwned[user1] = [Reward("Gold Coin", 1 ether), Reward("Silver Coin", 0.5 ether)];
// User1 transfers the first reward to User2
mysteryBox.transferReward(user2, 0);
// After transfer
rewardsOwned[user1] = [Reward("", 0), Reward("Silver Coin", 0.5 ether)];
rewardsOwned[user2] = [Reward("Gold Coin", 1 ether)];

When retrieving User1's rewards, an empty reward appears:

Reward[] memory user1Rewards = mysteryBox.getRewards(user1);
// user1Rewards[0] is Reward("", 0)
// user1Rewards[1] is Reward("Silver Coin", 0.5 ether)

This can lead to confusion when displaying rewards to users, as they may encounter unexpected empty entries.

Tools Used

Manual code review

Recommendations

Implement a more efficient removal mechanism that maintains the integrity of the rewards array without leaving gaps. One common approach is to swap the reward to be deleted with the last element and then remove the last element, reducing the array length by one.

Revised transferReward function:

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

This approach ensures that the rewardsOwned array remains contiguous and free of empty entries, improving efficiency and reducing potential confusion.

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!