Firstly, I encountered an issue with the testing: The test test_Call_PlayerWins() failed due to an OutOfFunds error, which occurred when the contract attempted to pay out 2 ether to the player. The contract did not have enough balance to fulfill this transaction, causing the test to fail.
Fund the Contract in the setUp() Function: In the setUp() function, add a line to fund the contract with Ether:
vm.deal(address(twentyOne), 10 ether); // Fund the contract with 10 ether
Check for Sufficient Funds in the Contract's Payout Logic: In the call() function of the TwentyOne contract, ensure there is a check that verifies the contract has enough balance to make the payout:
To resolve the issue, the contract needed to be funded with Ether in the test setup so that it could handle payouts during the call() function. Additionally, a check was added to ensure the contract has enough balance for payouts.
In addition there were vulnerabilities with the card drawing logic, where the contract wasn't correctly handling randomness within the game, and the handling of player actions (such as "hit" or "call") was dependent on external randomness, which caused difficulties in testing and deployment.
Reentrancy Protection: The contract uses a noReentrancy
modifier, which prevents reentrancy attacks by locking the contract during certain transactions. This ensures that a player cannot repeatedly call sensitive functions in a way that could exploit the contract.
Max Bet Check: A constant, MAX_BET
, was introduced to limit the amount of Ether that a player can stake. This prevents players from betting excessively, which could affect the contract's balance or lead to unexpected behavior.
Ether Transfer Check: Before transferring Ether to the player at the end of the game, a check was added to ensure that the contract has enough Ether to cover the payout. This helps prevent errors where the contract might try to send more than it can afford.
Access Control: The onlyPlayer
modifier ensures that only the player (who is interacting with the contract) can perform player-specific actions, such as drawing cards or ending the game. This prevents unauthorized users from interfering with a player's game.
Event Emissions: Several events, like PlayerLostTheGame
, PlayerWonTheGame
, and GameStarted
, were added to better track the game's progress and to notify when certain actions occur (e.g., when a player wins, loses, or a new game starts). These events help in debugging and monitoring the contract.
Simplified Randomness: New method of randomness was implemented based on block.timestamp
and block.prevrandao
. This generates a pseudo-random number based on block properties, ensuring that the randomness can still function locally without requiring external services.
Reentrancy Protection: The contract uses a noReentrancy
modifier, which prevents reentrancy attacks by locking the contract during certain transactions. This ensures that a player cannot repeatedly call sensitive functions in a way that could exploit the contract.
Max Bet Check: A constant, MAX_BET
, was introduced to limit the amount of Ether that a player can stake. This prevents players from betting excessively, which could affect the contract's balance or lead to unexpected behavior.
Ether Transfer Check: Before transferring Ether to the player at the end of the game, a check was added to ensure that the contract has enough Ether to cover the payout. This helps prevent errors where the contract might try to send more than it can afford.
Access Control: The onlyPlayer
modifier ensures that only the player (who is interacting with the contract) can perform player-specific actions, such as drawing cards or ending the game. This prevents unauthorized users from interfering with a player's game.
Event Emissions: Several events, like PlayerLostTheGame
, PlayerWonTheGame
, and GameStarted
, were added to better track the game's progress and to notify when certain actions occur (e.g., when a player wins, loses, or a new game starts). These events help in debugging and monitoring the contract.
Simplified Randomness: New method of randomness was implemented based on block.timestamp
and block.prevrandao
. This generates a pseudo-random number based on block properties, ensuring that the randomness can still function locally without requiring external services.
The changes made to the smart contract enhance its security, gameplay experience, and operational integrity. These updates:
Improved Security: By introducing reentrancy protection and ensuring proper checks before transferring funds, the contract is now more resilient against malicious attacks. This makes the game safer for users and ensures that their funds are protected.
Better User Experience: The implementation of the MAX_BET
constant and the onlyPlayer
modifier restricts unauthorized actions and ensures that players can only wager amounts within the defined limits. This helps maintain fairness and prevents abuse.
Transparency: The addition of events like GameStarted
, PlayerHit
, and others allows for better tracking of game events, making it easier for players and developers to monitor the game's progress. This improves transparency and helps with debugging and auditing the contract.
Reliable Payouts: The added check for sufficient contract balance ensures that payouts are reliable and prevents the contract from attempting payouts it can't cover, reducing the risk of failed transactions.
Modifiers: Solidity modifiers like noReentrancy
and onlyPlayer
are used to enhance security and restrict access to certain functions.
Events: Events like GameStarted
, PlayerHit
, PlayerWonTheGame
, etc., are utilized to emit important game state changes and provide transparency.
Ether (ETH): Ether is used as the staking currency for the game and is transferred to the winner or refunded under certain conditions.
forge test -vvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 3 tests for test/TwentyOne.t.sol:TwentyOneTest
[PASS] test_Call_PlayerWins() (gas: 1182064)
Logs:
Player's cards before call:
Card: 23
Card: 43
Mocking dealer's behavior with hand total of 18.
Player's initial balance: 9000000000000000000
Player call was successful.
Player's final balance: 11000000000000000000
Player's cards after call:
[PASS] test_Hit() (gas: 1267605)
[PASS] test_StartGame() (gas: 1255720)
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 12.20ms (6.41ms CPU time)
Ran 1 test suite in 91.01ms (12.20ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)
Fix the code as follow:
Randomness Manipulation: The randomness mechanism relies on block.timestamp, msg.sender, and block.prevrandao, which may be predictable in certain scenarios. Consider using Chainlink VRF or another oracle for more secure randomness.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.