The guess
function generates an "answer" using block.timestamp
, block.difficulty
, and msg.sender
, which are predictable within the same transaction. An attacker can compute the answer in real-time and call guess
with the correct value, allowing them to repeatedly drain 1 ether per successful call. This breaks the intended game logic and leads to loss of funds.
Use a secure random number generator such as Chainlink VRF, or implement a commit-reveal scheme to prevent on-chain predictability. Avoid relying solely on block variables for randomness.
Line 8 – 12
The balance
variable is of type uint8
(range 0-255). The increment
and decrement
functions perform unchecked arithmetic operations. When balance
is 255, calling increment
with any positive value
will cause an overflow (e.g., 255 + 1 = 0). Similarly, decrementing when balance
is 0 will underflow (e.g., 0 - 1 = 255). This leads to incorrect state updates and violates the expected accounting logic.
Use SafeMath for uint8 operations or upgrade to Solidity ^0.8.0+ which has built-in overflow checks. Add explicit checks (e.g., require(balance + value <= type(uint8).max, "Overflow")
in increment
and require(value <= balance, "Underflow")
in decrement
).
Line 11 – 17
The claimThrone
function makes an external call to the previous king before updating the contract's state. This allows a malicious king contract to reenter the function, leading to inconsistent state updates and potential fund loss. Attackers can exploit this to bypass payment checks, drain contract funds, or manipulate the king and balance variables incorrectly.
Follow the checks-effects-interactions pattern. Update the balance
and king
state variables before making the external call. Save the previous king and balance in local variables, update the state, then send Ether to the previous king.
Line 11 – 15
If the current king
is a contract that cannot receive Ether (e.g., lacks a payable fallback function), the transfer in claimThrone
will fail, reverting the transaction. This prevents any new user from becoming the king, effectively locking the contract. An attacker can exploit this by setting a malicious king contract to cause a denial-of-service.
Use a withdrawal pattern where the previous king must withdraw their balance manually. Alternatively, remove the require(sent, ...)
statement, but this may allow kings to not receive their dues. Ensure the king address can handle Ether transfers before allowing it to become king.
Line 11 – 12
The guess
function generates an "answer" using block.timestamp, block.difficulty, and msg.sender, which are predictable/controllable by miners or attackers. An attacker can compute the answer in advance by submitting a transaction that executes in the same block, allowing them to always win and drain the contract's ETH. This makes the game trivially exploitable.
Use a commit-reveal scheme with a secure randomness source (e.g., Chainlink VRF) that cannot be predicted by attackers. Avoid relying solely on on-chain data like block variables for critical randomness.
Line 8 – 12
The contract uses uint8 for balance, which can hold values from 0 to 255. The increment and decrement functions do not check for overflows/underflows. In Solidity <0.8.0, arithmetic operations wrap around on overflow. For example, if balance is 255 and increment(1) is called, it will wrap to 0 instead of reverting. Similarly, decrementing 0 by 1 will underflow to 255. This leads to incorrect accounting of the balance, which is a high-risk vulnerability as it fundamentally breaks the contract's state logic.
Upgrade to Solidity ≥0.8.0 which has built-in overflow checks, or implement explicit checks using SafeMath for uint8. For example:
// Before 0.8.0, using SafeMath
require(balance + value <= 255, "Overflow");
balance += value;
The contract sends Ether to the previous king before updating the state variables (balance and king). An attacker can become the king and then, in the fallback function of the receiving contract, re-enter the claimThrone function. Since the state hasn't been updated yet, the attacker can repeatedly claim the throne, causing the contract to send out the old balance multiple times, potentially draining the contract's funds.
Update the state variables (balance and king) before making the external call. This follows the checks-effects-interactions pattern to prevent reentrancy attacks.
Line 11 – 15