Description
-
block.timestamp (or block.timestamp / now) is a global variable in Solidity that represents the Unix timestamp of the current block. While commonly used for time-based logic, it is important to understand its limitations.
-
The issue is that block.timestamp is not perfectly precise and is susceptible to manipulation by miners. Miners have a small degree of control over the timestamp of the blocks they mine; they can slightly adjust it (typically within a range of a few seconds to a few minutes relative to real-world time) to influence time-sensitive operations. If a miner has a direct economic incentive, they might attempt to use this to gain an advantage (e.g., by being the first to enter a time-gated event or extending a deadline).
SLITHER OUTPUT:
## timestamp Impact: Low Confidence: Medium - [ ] ID-17
[FestivalPass.createPerformance(uint256,uint256,uint256)](src/FestivalPass.sol#L88-L103) uses timestamp for comparisons Dangerous comparisons: - [require(bool,string)(startTime > block.timestamp,Start time must be in the future)](src/FestivalPass.sol#L93) src/FestivalPass.sol#L88-L103 - [ ] ID-18 [FestivalPass.attendPerformance(uint256)](src/FestivalPass.sol#L106-L117) uses timestamp for comparisons
Dangerous comparisons:
- [require(bool,string)(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN,Cooldown period not met)](src/FestivalPass.sol#L110)
src/FestivalPass.sol#L106-L117
- [ ] ID-19
[FestivalPass.isPerformanceActive(uint256)](src/FestivalPass.sol#L139-L144) uses timestamp for comparisons
Dangerous comparisons: - [perf.startTime != 0 && block.timestamp >= perf.startTime && block.timestamp <= perf.endTime](src/FestivalPass.sol#L141-L143)
src/FestivalPass.sol#L139-L144
src/FestivalPass.sol#L93
require(startTime > block.timestamp, "Start time must be in the future");
src/FestivalPass.sol#L110
require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");
src/FestivalPass.sol#L141-L143
return block.timestamp >= perf.startTime && block.timestamp <= perf.endTime;
Risk
Likelihood:
-
This will occur if a miner finds a profitable opportunity to slightly manipulate the block timestamp to their advantage for time-sensitive features.
-
This will occur consistently in highly competitive, time-critical scenarios where a small time difference could determine success or failure.
Impact:
-
For very short-duration events or highly precise timing mechanisms, miner manipulation could lead to a minor shift in when an action becomes available or unavailable.
-
While generally low impact for events spanning hours or days, it poses a risk for very granular time-based access control or competitive actions.
-
The contract's time-dependent logic might not execute at the exact intended real-world time, leading to slight deviations.
Proof of Concept
Recommended Mitigation
(Review all block.timestamp usages. For the current use cases (performance start/end times, cooldowns), the impact is likely low, but it's a known consideration for high-value or highly competitive time-based logic.)
- remove this code
+ add this code
@@ -92,7 +92,10 @@
function createPerformance(uint256 _performanceId, uint256 startTime, uint256 endTime) public onlyOwner {
require(_performanceId != 0, "Performance ID cannot be zero");
require(startTime < endTime, "Start time must be before end time");
- require(startTime > block.timestamp, "Start time must be in the future"); // @> Consider if precision is critical
+ // For non-critical timing, block.timestamp is generally acceptable.
+ // For highly time-sensitive or competitive scenarios, consider:
+ // 1. Using a decentralized oracle (e.g., Chainlink Keepers/Oracles) for time.
+ // 2. Designing game theory such that miner manipulation is not profitable or impactful.
require(performances[_performanceId].startTime == 0, "Performance already exists");
performances[_performanceId] = Performance({