TwentyOne

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

Draw Scenarios in call() Function Incorrectly Treated as Loss for Players

Summary

In the call() function of the TwentyOne contract, draw scenarios (when the player's hand equals the dealer's hand) are treated as a loss for the player. This behavior violates standard blackjack rules, where a draw results in a "push," meaning the player's bet should be returned without any winnings or losses. This issue causes unfair losses for players and undermines trust in the platform.

Vulnerability Details

The call() function lacks explicit logic for handling draw scenarios. When the player's hand equals the dealer's hand, the contract defaults to the final else statement, which treats the draw as a loss:

else {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, false);
}

This implementation results in the player's deposit being forfeited even though neither side won the game.


Example Scenario

Initial Setup:

  • The player's hand total is 20.

  • The dealer's hand total is also 20.

Execution:

  • The player calls the call() function.

  • The else clause is executed, treating the draw as a loss.

Outcome:

  • The player loses their 1 ETH bet, which should have been refunded in a draw scenario.

Impact

  • Unfair Gameplay: Players lose their bet even when they tie with the dealer, leading to dissatisfaction.

  • Negative User Experience: Players may feel cheated, reducing trust in the platform.

  • Violation of Blackjack Rules: Standard rules dictate that a draw (push) should result in no net loss or gain for the player.

Tools Used

Manual Review

Recommendations

Add Draw Logic to call() Function:
Update the function to handle draw scenarios explicitly. If the player's hand equals the dealer's hand, emit a specific event and refund the player's bet.

Updated call() Function:

function call() public {
require(
playersDeck[msg.sender].playersCards.length > 0,
"Game not started"
);
uint256 playerHand = playersHand(msg.sender);
// Calculate the dealer's threshold for stopping (between 17 and 21)
uint256 standThreshold = (uint256(
keccak256(
abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)
)
) % 5) + 17;
// Dealer draws cards until their hand reaches or exceeds the threshold
while (dealersHand(msg.sender) < standThreshold) {
uint256 newCard = drawCard(msg.sender);
addCardForDealer(msg.sender, newCard);
}
uint256 dealerHand = dealersHand(msg.sender);
// Determine the winner
if (dealerHand > 21) {
emit PlayerWonTheGame(
"Dealer went bust, players winning hand: ",
playerHand
);
endGame(msg.sender, true);
} else if (playerHand > dealerHand) {
emit PlayerWonTheGame(
"Dealer's hand is lower, players winning hand: ",
playerHand
);
endGame(msg.sender, true);
} else if (playerHand == dealerHand) {
emit PlayerLostTheGame(
"Draw game: No winner",
dealerHand
);
endGame(msg.sender, false, true); // Pass a flag for a draw
} else {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, false, false);
}
}
  • Update endGame Function:
    Modify endGame to handle draws and refund the player's bet if the game ends in a draw.

    Updated endGame Function:

    function endGame(address player, bool playerWon, bool isDraw) 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);
    } else if (isDraw) {
    payable(player).transfer(1 ether); // Refund the player's bet
    emit FeeWithdrawn(player, 1 ether);
    }
    }
Updates

Lead Judging Commences

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

Tie case

Support

FAQs

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