Summary
When player hand and dealer hand are tied, the player will loses. I think it sould be tied.
Vulnerability Details
In the call()
function determine the winnder by comparing player hand and dealer hand.
TwentyOne.sol
function call() public {
require(
playersDeck[msg.sender].playersCards.length > 0,
"Game not started"
);
uint256 playerHand = playersHand(msg.sender);
uint256 standThreshold = (uint256(
keccak256(
abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)
)
) % 5) + 17;
while (dealersHand(msg.sender) < standThreshold) {
uint256 newCard = drawCard(msg.sender);
addCardForDealer(msg.sender, newCard);
}
uint256 dealerHand = dealersHand(msg.sender);
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 the playerHand
and dealerHand
are equal, it should be tied and return playing cost to player.
Impact
Unfair condition or unclear the game rule in case of the player hand and dealer hand is equal.
Tools Used
Manual code reading.
Recommendations
I recommend adding the handling in case of player and dealer are tied for example:
TwentyOne.sol
function call() public {
require(
playersDeck[msg.sender].playersCards.length > 0,
"Game not started"
);
uint256 playerHand = playersHand(msg.sender);
uint256 standThreshold = (uint256(
keccak256(
abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)
)
) % 5) + 17;
while (dealersHand(msg.sender) < standThreshold) {
uint256 newCard = drawCard(msg.sender);
addCardForDealer(msg.sender, newCard);
}
uint256 dealerHand = dealersHand(msg.sender);
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) {
[...]
payable(player).transfer(1 ether);
endGame(msg.sender, false);
}else {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, false);
}
}
Can do it better by changing the end game structure to recieve playerWon
parameter that can manage more than 2 case, for example:
TwentyOne.sol
function call() public {
[...]
if (dealerHand > 21) {
emit PlayerWonTheGame(
"Dealer went bust, players winning hand: ",
playerHand
);
endGame(msg.sender, 1);
} else if (playerHand > dealerHand) {
emit PlayerWonTheGame(
"Dealer's hand is lower, players winning hand: ",
playerHand
);
endGame(msg.sender, 1);
} else if (playerHand == dealerHand) {
[...]
endGame(msg.sender, 2);
}else {
emit PlayerLostTheGame(
"Dealer's hand is higher, dealers winning hand: ",
dealerHand
);
endGame(msg.sender, 0);
}
}
[...]
function endGame(address player, uint256 playerWon) internal {
delete playersDeck[player].playersCards;
delete dealersDeck[player].dealersCards;
delete availableCards[player];
if (playerWon == 1) {
payable(player).transfer(2 ether);
emit FeeWithdrawn(player, 2 ether);
}
if (playerWon == 2) {
payable(player).transfer(1 ether);
emit FeeWithdrawn(player, 1 ether);
}
}