Mystery Box

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

Possible DoS during expanded period of time after deployment

Description:

Impact: High
Likelihood: Low

The MysteryBox::claimAllRewards() function is vulnerable to DoS, since no parameters are set of any minimum amount of players before rewards can be redeemed. Should any player draft a SilverCoin or especially a GoldCoin during the initial phase of mystery box the contract is not able to pay the reward.

Vulnerable Code:

function openBox() public {
require(boxesOwned[msg.sender] > 0, "No boxes to open");
// Generate a random number between 0 and 99
// @audit-h garbage random
uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender))) % 100;
// Determine the reward based on probability
if (randomValue < 75) {
// 75% chance to get Coal (0-74)
rewardsOwned[msg.sender].push(Reward("Coal", 0 ether));
} else if (randomValue < 95) {
// 20% chance to get Bronze Coin (75-94)
rewardsOwned[msg.sender].push(Reward("Bronze Coin", 0.1 ether));
} else if (randomValue < 99) {
// 4% chance to get Silver Coin (95-98)
@> rewardsOwned[msg.sender].push(Reward("Silver Coin", 0.5 ether));
} else {
// 1% chance to get Gold Coin (99)
@> rewardsOwned[msg.sender].push(Reward("Gold Coin", 1 ether));
}
// Doesnt Follow CEI
boxesOwned[msg.sender] -= 1;
}

Impact:

Users of the protocol might not be able to withdraw their rewards for an unknown amount of time.

Tools Used:

Manual Review, ChatGPT for the math

Proof of Concept:

Understanding the Scenario:

  • 10 players participate in the game.

  • Each player pays 0.1 ETH to join.

  • The total collected funds are 10 * 0.1 ETH = 1 ETH.

  • The win probabilities are:

    • 75% chance of winning nothing

    • 20% chance of winning 0.1 ETH

    • 4% chance of winning 0.5 ETH

    • 1% chance of winning 1 ETH

Calculating Expected Winnings per Player:

  • Expected winnings = (0.75 * 0) + (0.20 * 0.1) + (0.04 * 0.5) + (0.01 * 1)

  • Expected winnings = 0 + 0.02 + 0.02 + 0.01

  • Expected winnings = 0.05 ETH

Calculating Total Prize Pool:

  • Total prize pool = 0.05 ETH/player * 10 players

  • Total prize pool = 0.5 ETH

Assessing the Risk of Insufficient Funds:

  • Difference: 1 ETH (collected funds) - 0.5 ETH (expected prize pool) = 0.5 ETH

With this payout structure, the probability that the game will not be able to pay out the rewards (i.e., the total payout exceeds the 1 ETH collected) is approximately 13.47%. (Thanks, ChatGPT)

Additionally the following test shows such a scenario. The First user buying a mystery box found a silver coin worth 0.5 ether. The user could obviously not redeem his reward.

function testFuzzForNotEnoughFunds(address user) public {
vm.warp(1641070800);
vm.deal(user, 0.2 ether);
vm.deal(address(mysteryBox), 0.1 ether);
vm.startPrank(user);
mysteryBox.buyBox{value: 0.1 ether}();
mysteryBox.openBox();
mysteryBox.claimAllRewards();
vm.stopPrank();
}
├─ [9118] MysteryBox::claimAllRewards()
│ ├─ [0] 0x43B35Cb79e7Da39eBB77909B23c82848C53749e0::fallback{value: 500000000000000000}()
│ │ └─ ← [OutOfFunds] EvmError: OutOfFunds
│ └─ ← [Revert] revert: Transfer failed
└─ ← [Revert] revert: Transfer failed
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.09ms (568.20µs CPU time)

Used Parameters for this Test in foundry.toml:

[fuzz]
runs = 256
seed = "0x01"

Conclusion:

With the win probabilities, the expected total prize pool is now 0.5 ETH, which is exactly equal to the total collected funds. This means that the game is theoretically balanced in terms of expected payouts. However, it's important to note that:

  • Randomness: The actual outcomes of the game can vary significantly from the expected values due to randomness. There's still a possibility of the prize pool exceeding the collected funds in a particular round.

  • Large Deviations: While the expected prize pool is 0.5 ETH, there's a chance of rare events where the actual prize pool is much higher or lower than expected.

Recommended Mitigation:

Set an initial deposit of 0.5 ETH or better 1 ETH into the contract to cover this edge cases.

Updates

Appeal created

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Protocol should have a higher initial balance to prevent prize withdrawing problems

Support

FAQs

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