TwentyOne

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

[L-01] Missing Game Timeout Mechanism Allows Games to Remain Indefinitely Incomplete

Summary

The TwentyOne contract lacks a timeout mechanism for abandoned games. While players can always complete their games by calling hit() or call(), there's no mechanism to handle scenarios where players abandon their games without completing them. This could lead to accumulated incomplete games and ETH sitting idle in the contract.

Vulnerability Details

Location: src/TwentyOne.sol

The contract allows players to start a game by depositing 1 ETH but provides no mechanism to handle abandoned games. While players can always return to complete their games, there's no way to resolve situations where players abandon their games entirely:

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

The vulnerability has several implications:

  1. Contract State

    • Abandoned games remain in contract state indefinitely

    • No way to clear inactive games

    • Contract storage bloat from unfinished games

  2. Game Operation

    • No incentive for players to properly complete games

    • No mechanism to resolve abandoned games

    • Contract state becomes increasingly cluttered over time

  3. User Experience

    • No way to distinguish between active and abandoned games

    • Players who forget about games have no reminder mechanism

    • No automated cleanup of inactive games

Tools Used

  • Manual Code Review

  • Foundry Testing Framework

  • Custom timeout scenario tests

Recommendations

  1. Add Timeout Mechanism:

struct GameState {
uint256[] playersCards;
uint256 startTime;
bool isActive;
}
mapping(address => GameState) public games;
function startGame() public payable returns (uint256) {
address player = msg.sender;
require(msg.value >= 1 ether, "not enough ether sent");
require(!games[player].isActive, "player has active game");
games[player].startTime = block.timestamp;
games[player].isActive = true;
initializeDeck(player);
// ... rest of current startGame logic ...
}
function resolveTimedOutGame(address player) public {
require(games[player].isActive, "no active game");
require(
block.timestamp >= games[player].startTime + 24 hours,
"game not timed out yet"
);
// Return the player's stake
payable(player).transfer(1 ether);
// Clean up game state
delete games[player];
delete playersDeck[player];
delete dealersDeck[player];
delete availableCards[player];
emit GameTimedOut(player);
}
  1. Add Game Status Tracking:

    • Track game start times

    • Implement game state enumeration

    • Add timeout status checks

  2. Improve Game Flow:

modifier activeGameOnly() {
require(games[msg.sender].isActive, "no active game");
require(
block.timestamp < games[msg.sender].startTime + 24 hours,
"game has timed out"
);
_;
}
function hit() public activeGameOnly {
// ... existing hit logic ...
}
function call() public activeGameOnly {
// ... existing call logic ...
}
  1. Add Events and Monitoring:

event GameStarted(address player, uint256 timestamp);
event GameTimedOut(address player);
  1. Documentation Updates:

    • Document timeout period

    • Explain timeout resolution process

    • Add warnings about game abandonment

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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