BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Reliance on block.timestamp for tournament timing introduces manipulation risk

Root + Impact

Description

  • Normally, the contract should rely on a secure, predictable time reference to control when the tournament starts or ends.

  • However, the current implementation uses block.timestamp to determine timing. Since miners or validators can adjust timestamps slightly within allowed margins, it introduces the possibility of timing manipulation around event cutoff moments.

// Root cause in the codebase with @> marks to highlight the relevant section
function deposit(uint256 assets, uint256 teamId) external {
@> require(block.timestamp < tournamentStart, "Tournament already started");
_deposit(assets, teamId);
}
function finalizeTournament(uint256 _winnerTeamId) external onlyOwner {
@> require(block.timestamp >= tournamentEnd, "Tournament not ended yet");
winnerTeamId = _winnerTeamId;
}

Risk

Likelihood:

  • Occurs whenever a miner can gain advantage by altering the block timestamp slightly (usually ±15 seconds).

  • Becomes more relevant when tournament start/end conditions have tight timing constraints.

Impact:

  • Attackers could make deposits just before or after the intended cutoff.

  • This may allow some users to participate when the tournament should be closed or prevent legitimate entries from being accepted.

Proof of Concept

Observed Effect:
If a miner includes the transaction in a block with a slightly altered timestamp, the contract could be finalized earlier or later than intended.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract PoC_TimestampManipulation {
uint256 public endTime = block.timestamp + 60;
bool public closed;
function finalize() external {
require(block.timestamp >= endTime, "Not yet ended");
closed = true;
}
}

Recommended Mitigation

**Explanation: **Instead of block.timestamp, use block.number for timing logic and convert to approximate durations based on average block times. This reduces miner control over timing-dependent logic.

function deposit(uint256 assets, uint256 teamId) external {
- require(block.timestamp < tournamentStart, "Tournament already started");
+ require(block.number < startBlockNumber, "Tournament already started");
_deposit(assets, teamId);
}
function finalizeTournament(uint256 _winnerTeamId) external onlyOwner {
- require(block.timestamp >= tournamentEnd, "Tournament not ended yet");
+ require(block.number >= endBlockNumber, "Tournament not ended yet");
winnerTeamId = _winnerTeamId;
}
Updates

Appeal created

bube Lead Judge 21 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!