TwentyOne

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

Design flaw in game logic

Design flaw in game logic

Description: After the contract was deployed, it starts with a balance of 0 ETH. When a player begins a game and wins instantly, the balance may be insufficient to pay out, as the contract's only available balance is the original wager of 1 ETH. Additionally, due to variance, there are scenarios where the contract's balance can drop below 1 ETH, even when it is adequately funded.

To address this issue, I conducted a Monte Carlo simulation, which indicated that an initial funding of approximately 50 ETH is necessary to ensure the contract does not fall victim to variance in 99% of cases. Increasing the initial funding further can help reduce the likelihood of the contract's balance dropping below 1 ETH.

Impact: When the contracts balance is under 1 ETH, the contract fails to payout the player, therefore greatly impacting the contracts desired functionality.

Recommended Mitigations: Modify the constructor to require an initial msg.value corresponding to the desired initial funding. Additionally, to further safeguard the contract against unlikely scenarios where the balance may be insufficient, include a check in the TwentyOne::startGame() function to ensure that the contract's balance remains above 1 ETH.

TwentyOne::constructor() example:

error InsufficientFunding();
constructor() payable{
if(msg.value < 50 ether){
revert InsufficientFunding();
}
}

TwentyOne::startGame()example:

function startGame() public payable returns (uint256) {
require(address(this).balance >= 1 ether, "contract does not possess enough ether");
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);
}

PoC: The flaw is trivial to discover when running the test_Call_PlayerWins() test, in which your team seemed to have overseen that the test fails.
To really make sure that the root cause of the failing test is an insufficient contract Balance. I added the following line to the tests set-up to fund the contract:

vm.deal(address(twentyOne), 10 ether);

Now we can observe that the contract is not failing anymore but passes.

Updates

Lead Judging Commences

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

Insufficient balance for payouts / Lack of Contract Balance Check Before Starting Game

Contract Lacks Mechanism to Initialize or Deposit Ether

Support

FAQs

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