TwentyOne

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

`startGame()` card value randomness can be gamed

Summary

The startGame() function reveals the player's cardValue in the same transaction of placing the bets. This allows users to revert the transaction as long as the returned cardValue is not 20, and only play if the cardValue is 20 which increases the odds of winning to more than 60%. Hence, the card randomness can be gamed. Following are the scenarios that a player will win or lose if he plays with card value of 20.

Dealer Points Win/Lose
17 win
18 win
19 win
20 lose
21 lose
>21 win

Vulnerability Details

This function receives the bet and return the player's card value in the same transaction.

function startGame() public payable returns (uint256) {
address player = msg.sender;
require(msg.value >= 1 ether, "not enough ether sent");
initializeDeck(player);
uint256 card1 = drawCard(player);
uint256 card2 = drawCard(player);
addCardForPlayer(player, card1);
addCardForPlayer(player, card2);
return playersHand(player);
}

Impact

Revert transaction if returned cardValue is not 20.

function test_PlayerOnlyPlayif20() public {
vm.startPrank(player1);
uint256 cardValue = twentyOne.startGame{value: 1 ether}();
require(cardValue == 20, "Card value not 20");
twentyOne.call();
}

Results

Revert if not 20
[1250994] TwentyOneTest::test_PlayerOnlyPlayif20()
├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123)
│ └─ ← [Return]
├─ [1270359] TwentyOne::startGame{value: 1000000000000000000}()
│ └─ ← [Return] 9
├─ [0] console::log("balance: ", 9000000000000000000 [9e18]) [staticcall]
│ └─ ← [Stop]
└─ ← [Revert] revert: Card value not 20
Play if 20
[1377791] TwentyOneTest::test_PlayerOnlyPlayif20()
├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123)
│ └─ ← [Return]
├─ [1270379] TwentyOne::startGame{value: 1000000000000000000}()
│ └─ ← [Return] 20
├─ [0] console::log("balance: ", 9000000000000000000 [9e18]) [staticcall]
│ └─ ← [Stop]
├─ [126378] TwentyOne::call()
│ ├─ emit PlayerWonTheGame(message: "Dealer went bust, players winning hand: ", cardsTotal: 20)

Tools Used

Foundry

Recommendations

Implement Commit-Reveal scheme such as locking in the bets first then reveal the card value to players.

Updates

Lead Judging Commences

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

Revert a bad outcome

Support

FAQs

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