Summary
The canTimeoutReveal function has else block missing.
Vulnerability Details
If both players revealed (playerARevealed & playerBRevealed), then no one can claim a timeout.
Without the else, the function just falls through and implicitly returns nothing.
function canTimeoutReveal(uint256 _gameId) external view returns (bool canTimeout, address winnerIfTimeout) {
Game storage game = games[_gameId];
if (game.state != GameState.Committed || block.timestamp <= game.revealDeadline) {
return (false, address(0));
}
bool playerARevealed = game.moveA != Move.None;
bool playerBRevealed = game.moveB != Move.None;
if (playerARevealed && !playerBRevealed) {
return (true, game.playerA);
} else if (!playerARevealed && playerBRevealed) {
return (true, game.playerB);
} else if (!playerARevealed && !playerBRevealed) {
return (true, address(0));
}
return (false, address(0));
}
Impact
Runtime revert if both players have revealed
Tools Used
Manual review
Recommendations
Fixed Code:
function canTimeoutReveal(uint256 _gameId) external view returns (bool canTimeout, address winnerIfTimeout) {
Game storage game = games[_gameId];
if (game.state != GameState.Committed || block.timestamp <= game.revealDeadline) {
return (false, address(0));
}
bool playerARevealed = game.moveA != Move.None;
bool playerBRevealed = game.moveB != Move.None;
if (playerARevealed && !playerBRevealed) {
return (true, game.playerA);
} else if (!playerARevealed && playerBRevealed) {
return (true, game.playerB);
} else if (!playerARevealed && !playerBRevealed) {
return (true, address(0));
} else {
return (false, address(0));
}
}