TwentyOne

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

Incorrect Handling of Tie Scenarios in function `call()` in `TwentyOne.sol`.

Summary

The function call() in TwentyOne.sol does not handle the case where the player's hand (playersHand) ties with the dealer's hand (dealersHand).


Vulnerability Details

The issue arises in function call() during the comparison of the player's hand and the dealer's hand:

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 {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, false);
}
}

When playerHand equals dealerHand, the contract does not include a condition to handle the tie.


Impact

  • Unexpected game state


Tools Used

  • Manual Code Review.


Recommendations

  • Add Tie-Handling Logic: I recommend using standard Blackjack rules, where a tie typically results in the player retaining their bet. Modify the call() function to include an explicit condition for ties, ensuring that the player does not lose their bet in this scenario. For example:

    event PlayerTiedWithDealer(string message, uint256 cardsTotal);
    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) {
    // Dealer went bust, player wins
    emit PlayerWonTheGame("Dealer went bust, players winning hand: ", playerHand);
    endGame(msg.sender, true);
    } else if (playerHand > dealerHand) {
    // Player wins
    emit PlayerWonTheGame("Dealer's hand is lower, players winning hand: ", playerHand);
    endGame(msg.sender, true);
    } else if (playerHand == dealerHand) {
    // Tie: Refund the player's bet
    emit PlayerTiedWithDealer("It's a tie! ", playerHand);
    payable(msg.sender).transfer(msg.value); // Refund the player's original bet
    endGame(msg.sender, false); // End game without marking either player or dealer as a winner
    } else {
    // Dealer wins
    emit PlayerLostTheGame("Dealer's hand is higher, dealers winning hand: ", dealerHand);
    endGame(msg.sender, false);
    }
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.