TwentyOne

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

The calculation of `playerTotal` for player in `TwentyOne::playersHand` vs `dealerTotal` for dealer in `TwentyOne::dealersHand` is different, which makes the game unfair.

Summary

The dealersHand function awards 10 points to only 3 out of the 13 possible card numbers, when according to classic blacjack, 10 points should be awarded to 4 out of 13 cards: The king, queen, jack and the number 10. Meanwhile, the playersHand function observes this rule, creating an unfair situation for both the player and the protocol as a loss could have been a win and vice versa.

Calculation of dealer's hand:

function dealersHand(address player) public view returns (uint256) {
uint256 dealerTotal = 0;
for (uint256 i = 0; i < dealersDeck[player].dealersCards.length; i++) {
uint256 cardValue = dealersDeck[player].dealersCards[i] % 13;
@> if (cardValue >= 10) {
dealerTotal += 10;
} else {
dealerTotal += cardValue;
}
}
return dealerTotal;
}

Calculation of player's hand:

function playersHand(address player) public view returns (uint256) {
uint256 playerTotal = 0;
for (uint256 i = 0; i < playersDeck[player].playersCards.length; i++) {
uint256 cardValue = playersDeck[player].playersCards[i] % 13;
@> if (cardValue == 0 || cardValue >= 10) {
playerTotal += 10;
} else {
playerTotal += cardValue;
}
}
return playerTotal;
}

Vulnerability Details

Proof of Concept:

Proof of Code:

Place the below code into TwentyOneTest in TwentyOne.t.sol

function testDealerHandIsCalculatedWrongly() public {
//Assuming these are the player's and dealer's cards in a game
uint256[] memory playersCards = new uint256[]();
uint256[] memory dealersCards = new uint256[]();
playersCards[0] = 52;
playersCards[1] = 33;
dealersCards[0] = 13;
dealersCards[1] = 24;
//The algorithm used in `TwentyOne::playersHand` for getting the actual player's hand.
uint256 playerHand = 0;
for (uint256 i = 0; i < playersCards.length; i++) {
uint256 cardValue = playersCards[i] % 13;
if (cardValue == 0 || cardValue >= 10) {
playerHand += 10;
} else {
playerHand += cardValue;
}
}
//The algorithm used in getting the expected dealer's hand. It is the same algorithm used for getting the actual player's hand.
uint256 expectedDealerHand = 0;
for (uint256 i = 0; i < dealersCards.length; i++) {
uint256 cardValue = dealersCards[i] % 13;
if (cardValue == 0 || cardValue >= 10) {
expectedDealerHand += 10;
} else {
expectedDealerHand += cardValue;
}
}
//The algorithm used in `TwentyOne::dealersHand` for getting the actual dealer's hand
uint256 actualDealerHand = 0;
for (uint256 i = 0; i < dealersCards.length; i++) {
uint256 cardValue = dealersCards[i] % 13;
if (cardValue >= 10) {
actualDealerHand += 10;
} else {
actualDealerHand += cardValue;
}
}
//In this case the dealer loses when it should have won as the expected dealer hand is greater than the player's
console.log("Player hand: ", playerHand);
console.log("Expected dealer hand: ", expectedDealerHand);
console.log("Actual dealer hand: ", actualDealerHand);
}

Impact

The wrong hand is calculated for the dealer, making each game unfair.

Tools Used

Foundry suite

Recommendations

function dealersHand(address player) public view returns (uint256) {
uint256 dealerTotal = 0;
for (uint256 i = 0; i < dealersDeck[player].dealersCards.length; i++) {
uint256 cardValue = dealersDeck[player].dealersCards[i] % 13;
- if (cardValue >= 10) {
+ if (cardValue == 0 || cardValue >= 10) {
dealerTotal += 10;
} else {
dealerTotal += cardValue;
}
}
return dealerTotal;
}
Updates

Lead Judging Commences

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

Asymmetric calculation of hands is rigged in the player`s favor.

Support

FAQs

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