[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 {
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);
twentyOne.startGame{value: 1 ether}();
vm.mockCall(
address(twentyOne),
abi.encodeWithSignature("dealersHand(address)", player1),
abi.encode(18)
);
vm.mockCall(
address(twentyOne),
abi.encodeWithSignature("playersHand(address)", player1),
abi.encode(18)
);
uint256 initialPlayerBalance = player1.balance;
twentyOne.call();
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);
+ }
}