SCWE-031: Insecure use of Block Variables
Stable Version v1.0
This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).
Relationships¶
- CWE-682: Incorrect Calculation
https://cwe.mitre.org/data/definitions/682.html
Description¶
In blockchain networks like Ethereum, block variables (block.timestamp, block.number, block.difficulty / block.prevrandao post-merge, etc.) provide information about the current state of the blockchain. However, these values are not fully deterministic and can be manipulated by validators (or miners on PoW chains), leading to vulnerabilities in smart contracts.
Block timestamps are not guaranteed to be accurate or consistent, and validators (or miners on PoW chains) can influence them within a certain range. This can cause issues when contracts depend on precise timing for critical functionality, such as token distribution, access control, or other time-sensitive events.
Potential issues that arise from insecure timestamp usage include:
- Timestamp Manipulation: Validators (or miners on PoW) can slightly alter
block.timestampto influence time-sensitive logic (e.g., auctions, token distributions, staking rewards). - Predictable Randomness: Using
block.numberorblock.difficultyas a source of randomness allows attackers to predict and manipulate outcomes. - Exploitable Access Control: Contracts that rely on block timestamps for permissions or actions may be bypassed if timestamps are adjusted.
Remediation¶
- Avoid timestamp-based conditions: Where possible, use block numbers instead of timestamps. Block numbers are more reliable and less subject to manipulation.
- Use Oracles: For time-sensitive contracts, consider using trusted oracles to provide external time data.
Examples¶
Insecure Block Timestamp Usage- Timestamp-Based Deadlines¶
pragma solidity ^0.4.0;
contract TimestampExample {
uint public deadline;
function setDeadline(uint _deadline) public {
deadline = _deadline;
}
function checkDeadline() public view returns (string) {
if (block.timestamp > deadline) {
return "Deadline passed";
} else {
return "Deadline not passed";
}
}
}
In the above example, block.timestamp is used to compare with the deadline. This creates a potential vulnerability as validators (or miners on PoW) can manipulate the block timestamp within a predefined window (~15 seconds).
Fixed Block Timestamp Usage¶
pragma solidity ^0.4.0;
contract SafeTimestampExample {
uint public deadline;
uint public blockNumber;
function setDeadline(uint _deadline) public {
deadline = _deadline;
blockNumber = block.number;
}
function checkDeadline() public view returns (string) {
if (block.number > blockNumber + 1000) { // Assuming a reasonable number of blocks for a deadline
return "Deadline passed";
} else {
return "Deadline not passed";
}
}
}
block.number instead of block.timestamp. This makes the contract less susceptible to timestamp manipulation, as block numbers are more reliable and consistent.
Insecure Lottery Using block.timestamp¶
pragma solidity ^0.8.0;
contract InsecureLottery {
address[] public players;
function enter() public payable {
require(msg.value > 0.01 ether, "Minimum ETH required");
players.push(msg.sender);
}
function pickWinner() public {
uint index = uint(block.timestamp) % players.length; // Insecure: Predictable outcome
(bool ok, ) = payable(players[index]).call{value: address(this).balance}("");
require(ok, "Transfer failed");
}
}
block.timestamp is manipulable within a small range, validators (or miners on PoW chains) can influence the winner selection.
- Attack Vector: A validator (or miner on PoW) could reorder transactions to ensure a specific outcome
Secure Alternative (Commit-Reveal)¶
pragma solidity ^0.8.0;
contract SecureLottery {
mapping(address => bytes32) public commits;
mapping(address => bytes32) public revealed;
address[] public players;
function commit(bytes32 hash) external payable {
require(msg.value > 0.01 ether, "Minimum ETH required");
require(commits[msg.sender] == bytes32(0), "Already committed");
commits[msg.sender] = hash;
players.push(msg.sender);
}
function reveal(bytes32 secret) external {
require(keccak256(abi.encodePacked(secret)) == commits[msg.sender], "Invalid reveal");
revealed[msg.sender] = secret;
}
function pickWinner() external {
bytes32 combined = bytes32(0);
for (uint i = 0; i < players.length; i++) {
require(revealed[players[i]] != bytes32(0), "All must reveal");
combined = keccak256(abi.encodePacked(combined, revealed[players[i]]));
}
uint index = uint(combined) % players.length;
(bool ok, ) = payable(players[index]).call{value: address(this).balance}("");
require(ok, "Transfer failed");
}
}
block.prevrandao and block.timestamp are not safe for value-at-stake randomness — use VRF (e.g., Chainlink) or commit-reveal.