TwentyOne

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

[H-1] No Good Way to Fund the TwentyOne Game Contract Initially or Withdraw Funds

Summary

The contract does not support a payable constructor, receive(), or fallback() functions, rendering it essentially unplayable. Additionally, there is no mechanism to withdraw funds from the contract, causing any forfeited user Ether to remain permanently stuck.

Vulnerability Details

The contract lacks a straightforward way for the owner to fund the initial prize pool or withdraw rewards. While some workarounds exist, they are not intuitive, not documented as expected behavior, and may lead to undesirable consequences.

Potential Workarounds

  1. Using startGame() with Sufficient ETH:
    The owner could deploy the contract with no initial balance and fund it later by calling the startGame() function with enough Ether. However:

    • This approach requires unnecessary transactions.

    • It starts a game for the owner, which is not ideal.

    • There is no guarantee that another player will not have already played the game.

  2. Deploying to a Pre-Funded Address:
    The owner could deploy the contract to a predetermined address with existing ETH. However, this:

    • Requires advanced techniques.

    • Is not feasible in all cases.

Additionally, the inability to withdraw funds might be intentional but is uncommon and not explicitly stated in the contract documentation.

Impact

The game cannot be properly started or played if the TwentyOne contract has insufficient funds.

Tools Used

  • Manual Review

  • Foundry

  • ChatGPT

Recommendations

  1. Implement a Payable Constructor:
    Allow the deployer to set the initial contract balance upon deployment. Example:

    constructor() payable {
    // Initialize the contract with an initial balance
    }
  2. Add receive() and fallback() Functions:
    Enable the contract to receive funds directly and allow users to provide tips. Example:

    receive() external payable {
    // Handle direct Ether transfers
    }
    fallback() external payable {
    // Handle fallback scenarios
    }
  3. Implement a Withdrawal Mechanism:
    Allow the contract owner to withdraw forfeited funds while ensuring the contract retains sufficient balance for gameplay. Use OpenZeppelin's Ownable and onlyOwner for this. Example:

    function withdraw(uint256 amount) external onlyOwner {
    require(address(this).balance >= amount + MINIMUM_BALANCE, "Insufficient balance to withdraw");
    payable(owner()).transfer(amount);
    }
  4. Expose contract balance:

    function getContractBalance() external view returns (uint256) {
    return address(this).balance;
    }
  5. Update Documentation:
    Clearly document the intended methods for funding and withdrawing, and highlight any limitations or intentional design choices.

Updates

Lead Judging Commences

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

Owner has no method to withdraw

Contract Lacks Mechanism to Initialize or Deposit Ether

Support

FAQs

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