TwentyOne

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

Ether Locking Risk in Abandoned Games

Summary

The startGame() function allows players to initiate a game by depositing Ether. However, there is no mechanism to handle cases where players abandon their games without completing them. This leads to trapped funds in the contract, as there is no way to reclaim the locked Ether.

Here’s the relevant code snippet:

require(msg.value >= 1 ether, "not enough ether sent");

Once a player starts a game, their deposit is held in the contract, but if they fail to call hit() or call() to finish the game, the Ether remains locked indefinitely.

Vulnerability Details

  1. No Timeout Mechanism:

    • The contract lacks a timer to track the duration of inactivity. Players who start a game but never interact further can leave their funds locked.

    • Without an expiration mechanism, inactive games persist indefinitely, making it impossible to repurpose or refund the trapped Ether.

  2. Impact on Contract Balance:

    • Over time, the contract may accumulate a significant amount of locked funds from incomplete games. These funds cannot be accessed by the contract owner or returned to the players.

    • This creates a potential liquidity risk, especially if the contract’s main purpose is dependent on a balanced Ether flow.

  3. Potential Exploitation:

    • Malicious users could intentionally start games without completing them, disrupting the contract’s functionality by reducing its available Ether balance.

    • The accumulation of abandoned deposits might discourage new players from engaging with the game due to perceived inefficiency.

  4. Lack of Administrative Controls:

    • The contract does not provide an admin or owner function to reclaim funds from inactive games. This limits the flexibility of the contract to address such issues manually in the absence of automated solutions.


Impact

  • Severity: Medium to High

    • The primary impact is loss of user funds that are trapped indefinitely, which can harm the contract's reputation.

    • This issue also introduces operational inefficiencies, as the contract's balance may be artificially inflated by funds that cannot be utilized.

  • Example Scenario:

    1. A player calls startGame() and deposits 1 Ether to begin a game.

    2. The player does not call hit() or call() to complete the game.

    3. The deposited Ether remains locked in the contract with no mechanism to refund it or handle the abandoned game.


Tools Used

  • Manual Review: Identified the absence of timeout or recovery mechanisms in the contract logic.

  • Slither: Flagged the lack of Ether withdrawal logic for inactive accounts.

Recommendations

  1. Implement a Timeout Mechanism Introduce a timeout for inactive games to allow the contract to reclaim or refund funds after a set period. Use a mapping to track the start time of each player's game:

Updated Code for startGame():

mapping(address => uint256) private gameStartTime;
function startGame() public payable {
require(msg.value >= 1 ether, "not enough ether sent");
require(
gameStartTime[msg.sender] == 0 ||
block.timestamp > gameStartTime[msg.sender] + 1 days,
"Active game in progress"
);
gameStartTime[msg.sender] = block.timestamp;
initializeDeck(msg.sender);
uint256 card1 = drawCard(msg.sender);
uint256 card2 = drawCard(msg.sender);
addCardForPlayer(msg.sender, card1);
addCardForPlayer(msg.sender, card2);
}

Explanation:

  • Tracks the timestamp when a game starts for each player.

  • Prevents players from starting multiple games simultaneously.

  • Ensures that only games older than 24 hours can be considered inactive.

  1. Add a Reclamation Function Provide a public function to reclaim funds from abandoned games after a timeout period. This function ensures that the game state is reset and the player's deposit is refunded.

New reclaimFunds() Function:

function reclaimFunds(address player) public {
require(
block.timestamp > gameStartTime[player] + 1 days,
"Game still active"
);
require(
playersDeck[player].playersCards.length > 0,
"No active game for this player"
);
delete playersDeck[player].playersCards; // Clear the player's hand
delete dealersDeck[player].dealersCards; // Clear the dealer's hand
delete availableCards[player]; // Reset the available deck
gameStartTime[player] = 0; // Reset the game timer
payable(player).transfer(1 ether); // Refund the player's initial deposit
}

Explanation:

  • Validates that the game has been inactive for over 24 hours.

  • Clears all associated state variables for the player.

  • Refunds the player’s deposit to ensure fairness.

  1. Enhance Administrative Flexibility To further safeguard against abandoned games, consider adding an admin function to manually handle inactive games in case automated mechanisms fail.

Admin Function to Withdraw Abandoned Funds:

function withdrawAbandonedFunds(address player) public onlyOwner {
require(
block.timestamp > gameStartTime[player] + 7 days,
"Abandoned period not met"
);
require(
playersDeck[player].playersCards.length > 0,
"No active game for this player"
);
delete playersDeck[player].playersCards;
delete dealersDeck[player].dealersCards;
delete availableCards[player];
gameStartTime[player] = 0;
}
Additional Recommendations
Player Notification: Emit an event when a game is considered inactive, informing players of the timeout:

Additional Recommendations

  1. Player Notification: Emit an event when a game is considered inactive, informing players of the timeout:

emit GameTimedOut(player, block.timestamp);
  • Unit Testing: Write tests to ensure the timeout mechanism works as intended:

    • Validate that funds are reclaimable after the timeout.

    • Confirm that no active game can be reclaimed prematurely.

    • Test the admin’s ability to withdraw abandoned funds.

Closing Remarks

This vulnerability introduces the risk of Ether becoming locked indefinitely, disrupting the contract's functionality and user experience. By implementing a timeout mechanism, a reclamation function, and administrative controls, the contract can effectively handle inactive games and prevent the accumulation of trapped funds. These enhancements will significantly improve the contract's reliability and maintain user trust.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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