Smart contracts often use block.timestamp for time-sensitive logic — unlock schedules, auction deadlines, interest calculations, and randomness. But block proposers (formerly miners, now validators) can manipulate the timestamp within a tolerance window of ~12-15 seconds. This seemingly small window has been exploited to win lotteries, front-run auction endings, and manipulate interest accruals.
How Timestamp Manipulation Works
Block proposers set the timestamp when building a block. The only constraint is that it must be greater than the parent block's timestamp and within a reasonable boundary of the current time (~15 seconds). This gives them enough flexibility to game time-sensitive contracts.
// VULNERABLE: Lottery using timestamp as randomness
function drawWinner() external {
uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp)));
address winner = participants[random % participants.length];
// Validator can adjust timestamp to select themselves
}
// VULNERABLE: Auction with timestamp deadline
function bid() external payable {
require(block.timestamp < auctionEnd, "Auction ended");
// Validator can delay their own bid's block to just before deadline
// and exclude competing bids from the block
}
Timestamp-Dependent Vulnerabilities
| Pattern | Risk |
|---|---|
Randomness from block.timestamp | Validator predicts/manipulates outcome |
Exact time comparisons (==) | May never match; DoS risk |
| Short time windows (<5 min) | Validator can include/exclude within window |
| Interest calculation per-second | Timestamp skew affects accruals |
Prevention
// SECURE: Use Chainlink VRF for randomness
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
// SECURE: Use block.number instead of timestamp for ordering
require(block.number >= unlockBlock, "Still locked");
// SECURE: Use wide time windows (hours/days, not seconds)
require(block.timestamp >= auctionEnd + 1 hours, "Grace period");
// SECURE: Use commit-reveal for games
function commit(bytes32 hash) external { ... }
function reveal(uint256 value, bytes32 salt) external { ... }
- ✅ Never use
block.timestampfor randomness — use Chainlink VRF - ✅ Use
block.numberinstead of timestamp where possible - ✅ Avoid time windows shorter than 15 minutes
- ✅ Use
>= / <=not==for time comparisons - ✅ Account for 15-second tolerance in all time-sensitive logic
How Vultbase Detects Timestamp Issues
- Slither — Flags
block.timestampusage in comparisons and randomness - Pattern DB — 11 time manipulation patterns from real exploits
- Challenge Execution — Tests time-dependent logic with edge-case timestamps
Time-based logic needs special care. Audit your time-sensitive contracts before deployment.