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.