Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

Reentrancy Vulnerability in ETH Transfers

Description: The contract uses call inside the RockPaperScissors::_finishGame for ETH transfers without implementing the Checks-Effects-Interactions pattern, making it vulnerable to reentrancy attacks.

Impact: An attacker could potentially re-enter the contract and drain funds by repeatedly calling the vulnerable functions before state changes are applied.

Proof of Concept:

// Malicious contract to exploit reentrancy
contract ReentrancyExploit {
RockPaperScissors public game;
uint256 public gameId;
constructor(RockPaperScissors _game) {
game = _game;
}
function attack() external payable {
gameId = game.createGameWithEth{value: msg.value}(3, 5 minutes);
game.joinGameWithEth{value: msg.value}(gameId);
// Trigger reentrancy during payout
}
receive() external payable {
if (address(game).balance >= msg.value) {
game.timeoutReveal(gameId);
}
}
}

Recommended Mitigation:

// ... existing code ...
function _finishGame(uint256 _gameId, address _winner) internal {
Game storage game = games[_gameId];
game.state = GameState.Finished;
uint256 prize = 0;
uint256 fee = 0;
if (game.bet > 0) {
uint256 totalPot = game.bet * 2;
fee = (totalPot * PROTOCOL_FEE_PERCENT) / 100;
prize = totalPot - fee;
accumulatedFees += fee;
emit FeeCollected(_gameId, fee);
}
// Handle token prizes
if (game.bet == 0) {
winningToken.mint(_winner, 2);
} else {
winningToken.mint(_winner, 1);
}
// Send ETH prize last
if (prize > 0) {
(bool success,) = _winner.call{value: prize}("");
require(success, "Transfer failed");
}
emit GameFinished(_gameId, _winner, prize);
}
// ... existing code ...
Updates

Appeal created

m3dython Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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