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 about 1 year 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.

Give us feedback!