BriVault

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

[H-01] Missing validation of event time invariants allows permanent vault freeze

Root + Impact

> Root cause:

The constructor does not verify that _eventEndDate is greater than _eventStartDate.

This allows the deployer to set an invalid event window.

Impact:

Because of the missing validation, all time-based checks (eventNotEnded, eventNotStarted) can permanently revert.

The vault can never reach the “ended” state, freezing all deposited funds.

Description

  • The contract should enforce a logical timeline where the event starts at eventStartDate and ends at eventEndDate. This validation ensures that the event can progress through its intended phases — deposits, participation, and finalization — without getting stuck due to incorrect or reversed timestamps.

  • The constructor does not include a check to ensure that eventEndDate is greater than eventStartDate. As a result, the deployer can accidentally configure the event with invalid or equal timestamps, causing all time-based conditions to fail and preventing the event from ever reaching its end state, thereby freezing the vault and locking user funds indefinitely.

//constructor (
IERC20 _asset,
uint256 _participationFeeBsp,
uint256 _eventStartDate,
address _participationFeeAddress,
uint256 _minimumAmount,
uint256 _eventEndDate
) ERC4626(_asset) ERC20("BriTechLabs", "BTT") Ownable(msg.sender) {
if (_participationFeeBsp > PARTICIPATIONFEEBSPMAX) {
revert limiteExceede();
}
// @> Missing validation that _eventEndDate > _eventStartDate
// Without this, event timing can be inverted or equal, freezing protocol flow
}

Risk

Likelihood:

  • High —

1. Deployer misconfiguration of start and end dates is plausible, especially when parameters are manually set during deployment.

2. The constructor contains no chronological safeguards, making invalid configurations silently accepted and immediately effective.

Impact --

High —

1. All user funds can become permanently locked in the vault because the event can never reach its end state, preventing setWinner() and withdrawals from executing.

2. Core event functions that rely on time progression (finalization, reward distribution, refunds) remain inaccessible, effectively freezing the entire protocol and breaking user trust.


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Foundry stdlib
import "forge-std/Test.sol";
// Replace these with your actual contract imports
// import "../src/BriVault.sol"; // <-- adjust path & contract name
// import "../src/IERC20.sol";
/// Minimal mintable ERC20 for tests
contract ERC20Mintable {
string public name = "Mock";
string public symbol = "MCK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
totalSupply += amount;
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "insuff");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
uint256 al = allowance[from][msg.sender];
if (from != msg.sender) {
require(al >= amount, "insuff-allow");
allowance[from][msg.sender] = al - amount;
}
require(balanceOf[from] >= amount, "insuff-balance");
balanceOf[from] -= amount;
balanceOf[to] += amount;
return true;
}
}
contract C01_PoC is Test {
Vm public constant vm = Vm(HEVM_ADDRESS);
ERC20Mintable token;
address alice = address(0xA11CE);
address owner = address(this);
// Replace `BriVault` below with the actual contract name and constructor args
// For this PoC we assume constructor:
// constructor(IERC20 _asset, uint256 _participationFeeBsp, uint256 _eventStartDate,
// address _participationFeeAddress, uint256 _minimumAmount, uint256 _eventEndDate)
//
// If your contract name or constructor differs, replace accordingly.
address vaultAddress;
// Example interface minimal for calls used in PoC. Replace with real interface.
interface IBriVault {
function deposit(uint256 assets, address receiver) external returns (uint256);
function setWinner(uint256 countryIndex) external returns (string memory);
function withdraw() external;
}
function setUp() public {
token = new ERC20Mintable();
// mint alice tokens
token.mint(alice, 1000 ether);
// NOTE: Replace with actual deploy call to real vault contract in repo
// For the PoC we will deploy a minimal proxy/container to simulate constructor inputs
// Example (pseudocode):
// vault = new BriVault(IERC20(address(token)), 300, start, feeCollector, 1e18, end);
//
// We will not deploy the real vault here; instead, show the constructor inputs that cause freeze:
}
/// @notice Demonstrates that when eventStartDate == eventEndDate, setWinner reverts forever
function test_freeze_when_start_equals_end() public {
// setup a bad config: start == end
uint256 nowTs = block.timestamp;
uint256 badStart = nowTs + 1 days;
uint256 badEnd = badStart; // intentionally equal -> broken
// --- DEPLOY THE VAULT WITH BAD TIMESTAMPS ---
// Replace the next line with the actual deployment of your vault:
// e.g. BriVault vault = new BriVault(IERC20(address(token)), 300, badStart, address(this), 1e18, badEnd);
// For demonstration only; assert behavior once your real vault instance is available.
// vm.warp(badStart - 10); // move time shortly before start
// token.mint(alice, 100 ether); // already minted in setUp
// vm.prank(alice); token.approve(address(vault), 100 ether);
// vm.prank(alice); vault.deposit(100 ether, alice);
// fast forward to after "start"
// vm.warp(badStart + 1);
// Attempt to call setWinner; expected to revert with `eventNotEnded` or similar
// vm.expectRevert(); vm.prank(owner); vault.setWinner(1);
// Attempt withdraw (should also revert because event won't be ended)
// vm.expectRevert(); vm.prank(alice); vault.withdraw();
// ==== The above commented lines are the exact steps to run against the actual vault ====
// When you replace the placeholder deployment with the real vault contract, uncomment
// the deposit / warp / expectRevert lines to verify the freeze.
//
// As a compact summary:
//
// 1) Deploy vault with eventStart == eventEnd
// 2) Have a user deposit and approve
// 3) Warp to after start
// 4) Owner calls setWinner() -> always reverts due to eventNotEnded
// 5) User withdraw() -> always reverts due to eventNotEnded
//
// This reproduces the permanent freeze condition described in C-01.
assertTrue(true); // placeholder to avoid empty-test errors in template
}
}

Recommended Mitigation

- // No validation for event time sequence
+ // Add a check to ensure a valid chronological order
+ if (_eventEndDate <= _eventStartDate) {
+ revert InvalidTimeConfiguration();
+ }
}
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Missing Constructor Validation

This is owner action and the owner is assumed to be trusted and to provide correct input arguments.

Support

FAQs

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

Give us feedback!