TwentyOne

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

Player loses in situations where the dealer and player ties.

[H-1] Player loses in situations where the dealer and player ties.

Description: In cases where the player and dealer ties, it is expected that 1 ETH will be returned to the player such that the player incurs no net loss. However in the call function, it only considers the following scenarios:

function call() public {
// Existing code above
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);
}
}

Impact: Even when the dealer and player tie, the player is assumed to have lost, and will lose the 1 ETH deposit. This makes the game unfair to the player.

Proof of Concept:

Insert the following in TwentyOne.t.sol

function test_Call_PlayerLosesInTie() public {
vm.startPrank(player1); // Start acting as player1
// Start the game with 1 ether
twentyOne.startGame{value: 1 ether}();
// Mock the dealer's behavior to ensure the game results in a tie
vm.mockCall(
address(twentyOne),
abi.encodeWithSignature("dealersHand(address)", player1),
abi.encode(18) // Dealer's hand total is 18
);
// Simulate player's hand (player's hand total is also 18)
vm.mockCall(
address(twentyOne),
abi.encodeWithSignature("playersHand(address)", player1),
abi.encode(18) // Player's hand total is 18
);
uint256 initialPlayerBalance = player1.balance;
// Player calls to compare hands
twentyOne.call();
// Check that the player's balance has not changed (fails in case of a tie)
uint256 finalPlayerBalance = player1.balance;
assertEq(finalPlayerBalance, initialPlayerBalance);
vm.stopPrank();
}

Recommended Mitigation: Include logic to handle ties

event PlayerLostTheGame(string message, uint256 cardsTotal);
event PlayerWonTheGame(string message, uint256 cardsTotal);
+ event PlayerTiedWithDealer(string message, uint256 cardsTotal);
function endGame(
address player,
bool playerWon,
+ bool playerTied
) 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
}
+ else if (playerTied) {
+ payable(player).transfer(1 ether); // Transfer the prize to the player
+ emit FeeWithdrawn(player, 1 ether); // Emit the prize withdrawal event
+ }
}
function call() public {
// Existing code above
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 {
+ } else if (playerHand < dealerHand) {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, false);
}
+ else {
+ emit PlayerTiedWithDealer(
+ "Player tied with dealer, players head: ",
+ playerHand
+ );
+ 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.