TwentyOne

First Flight #29
Beginner FriendlyGameFiFoundrySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Users lose their playing cost without winning reward when there is not enough reward in the contract

Summary

The startGame() function is used to start playing the blackjack game without checking the ETH balance in the TwentyOne contract. The user must pay an ETH to play, so the user's wins are not awarded.

Vulnerability Details

The startGame() function allows users to play a game by not checking the reward in the TwentyOne contract.

TwentyOne.sol#L93

function startGame() public payable returns (uint256) {
address player = msg.sender;
require(msg.value >= 1 ether, "not enough ether sent");
initializeDeck(player);
uint256 card1 = drawCard(player);
uint256 card2 = drawCard(player);
addCardForPlayer(player, card1);
addCardForPlayer(player, card2);
return playersHand(player);
}

When the user wins the game, the reward must be paid to the user at the end of the game by the code payable(player).transfer(2 ether) in the endGame() function.

TwentyOne.sol#165

function endGame(address player, bool playerWon) internal {
delete playersDeck[player].playersCards; // Clear the player's cards
delete dealersDeck[player].dealersCards; // Clear the dealer's cards
delete availableCards[player]; // Reset the deck
if (playerWon) {
payable(player).transfer(2 ether); // Transfer the prize to the player
emit FeeWithdrawn(player, 2 ether); // Emit the prize withdrawal event
}
}

If the balance is not enough to pay a reward, the user's playing cost will stuck in the contract and will not get the winning reward.

Impact

Users lose their playing ETH without winning a reward.

Tools Used

Manual code reading.

Recommendations

I recommend checking that the contract has enough ETH to pay the winning reward before users start playing a game to prevent the lost playing cost and consider the case where multiple players are simultaneously.

The consideration of multiple user play at the same time can be checked by adding a tricker in the contract and increasing by 1 in the startGame() function in the following example code line 14 and decreasing it by 1 in the endGame() function in the following code line 28 then check the contract balance is enough to pay all of playing user in the startGame() function in the following example code line 6.

TwentyOne.sol

uint256 onPlaying;
[...]
function startGame() public payable returns (uint256) {
require(address(this).balance >= (1+(2*onPlaying)) * (10**18), "Ether balance is not enough to pay winning reward");
address player = msg.sender;
require(msg.value >= 1 ether, "not enough ether sent");
initializeDeck(player);
uint256 card1 = drawCard(player);
uint256 card2 = drawCard(player);
addCardForPlayer(player, card1);
addCardForPlayer(player, card2);
onPlaying++;
return playersHand(player);
}
[...]
function endGame(address player, bool playerWon) internal {
delete playersDeck[player].playersCards; // Clear the player's cards
delete dealersDeck[player].dealersCards; // Clear the dealer's cards
delete availableCards[player]; // Reset the deck
if (playerWon) {
payable(player).transfer(2 ether); // Transfer the prize to the player
emit FeeWithdrawn(player, 2 ether); // Emit the prize withdrawal event
}
onPlaying--;
}
Updates

Lead Judging Commences

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

Insufficient balance for payouts / Lack of Contract Balance Check Before Starting Game

Support

FAQs

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