Timestamp Dependence Remediation
How to eliminate exploitable timestamp dependence by replacing block.timestamp with block numbers, commit-reveal schemes, or off-chain randomness.
Timestamp Dependence Remediation
Overview
Timestamp dependence vulnerabilities arise when contract logic relies on block.timestamp for outcomes where a validator’s ability to adjust the timestamp by approximately 15 seconds creates an exploitable window. The remediation depends on the use case: replace with block numbers for duration calculations, or use verifiable randomness (Chainlink VRF) for random outcomes.
Related Detector: Timestamp Dependence
Recommended Fix
Before (Vulnerable)
// Lottery using block.timestamp — validators can withhold blocks to bias outcome
function pickWinner() external {
require(block.timestamp >= lotteryEndTime, "Lottery still active");
uint256 randomIndex = uint256(keccak256(abi.encodePacked(
block.timestamp, // Validator-influenceable
block.prevrandao,
msg.sender
))) % participants.length;
winner = participants[randomIndex];
}
After (Fixed)
// Option 1: Chainlink VRF for provably fair randomness
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
contract FairLottery is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
uint256 public randomResult;
function requestWinner() external {
require(block.timestamp >= lotteryEndTime);
// Request randomness from Chainlink VRF — unpredictable and verifiable
COORDINATOR.requestRandomWords(keyHash, subscriptionId, 3, 100000, 1);
}
function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override {
winner = participants[randomWords[0] % participants.length];
}
}
Alternative Mitigations
Block number instead of timestamp for duration tracking — block numbers increase monotonically and cannot be manipulated by validators (only delayed):
// Instead of: require(block.timestamp >= startTime + duration)
// Use: require(block.number >= startBlock + durationInBlocks)
uint256 public startBlock;
uint256 public constant DURATION_BLOCKS = 7200; // ~1 day at 12s/block
function isEnded() public view returns (bool) {
return block.number >= startBlock + DURATION_BLOCKS;
}
15-second tolerance — for non-critical time checks where 15 seconds of variance is acceptable, document the assumption and ensure the economic value at stake does not justify the manipulation cost:
// Acceptable: loan due date with 15-second tolerance is not exploitable
// because the economic value of 15 seconds of time is negligible
require(block.timestamp >= dueDate, "Loan not yet due");
// Document: validator timestamp manipulation is bounded to ~15s;
// this tolerance is acceptable for loan timing
Common Mistakes
Using block.timestamp for lotteries or random outcomes — any timestamp-based randomness where the expected gain exceeds the cost of 15-second block withholding is exploitable by validators.
Using block.timestamp as a nonce or unique identifier — two transactions in the same block share the same block.timestamp. Never use timestamp as a unique key.
Tight deadline enforcement — enforcing a deadline within seconds using block.timestamp is unreliable because validators can bias timestamps by up to 15 seconds.