BriVault

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

`BriVault::deposit` does not mint `ERC4626` tokens to the receiver, but to `msg.sender`

Description

  • According to the ERC4626 standard, deposit(assets, receiver) should: charge the fee and transfer assets minus fee to the vault from msg.sender, and mint shares to the receiver proportional to the assets deposited.

  • stakedAsset[receiver] is correctly set for the receiver.
    But the shares are minted to msg.sender:

function deposit(uint256 assets, address receiver) public override returns (uint256) {
(.... )
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
@> _mint(msg.sender, participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}

Risk

Likelihood: High

  • Occurs every time someone deposits on behalf of another address (receiver != msg.sender), a typical ERC4626 functionality.

Impact: High

  • “Ghost bet”: the receiver can join but with 0 shares → does not participate in the winners pool.


  • The depositor (msg.sender) keeps the shares but cannot join (does not have stakedAsset) → cannot withdraw as a winner.

  • Calculation mismatches (totalParticipantShares, totalWinnerShares), which can affect other users (e.g., withdrawal proportions).

Proof of Concept

  1. user1 deposits 10 tokens but specifies alice as the receiver.

  2. Calculate the fee and expected shares.

  3. Verify the inconsistency.

  4. Alice joins a team.

  5. The bet registered for alice is ZERO, even though a deposit was made on her behalf.

  6. Other users deposit and join a country.

  7. The event ends and the winner is set (alice's team).

  8. Alice tries to withdraw. Since userSharesToCountry[alice][winner]
    was 0 (because she had no shares), the calculation makes alice receive 0 tokens.

function test_receiver_Doesnt_Recieve_ERC4626_Tokens() public {
// 1
vm.startPrank(user1);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, alice);
vm.stopPrank();
// 2
uint256 fee = calculateFee(10 ether);
uint256 shares = 10 ether - fee;
// 3
assertEq(briVault.balanceOf(alice), 0);
assertEq(briVault.balanceOf(user1), shares);
// 4
vm.startPrank(alice);
uint256 countryId = 10;
vm.expectEmit();
emit BriVault.joinedEvent(alice, countryId);
briVault.joinEvent(10);
// 5
uint256 betAmount = briVault.userSharesToCountry(alice, countryId);
assertEq(betAmount, 0);
vm.stopPrank();
// 6
vm.startPrank(user2);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, user2);
briVault.joinEvent(1);
vm.stopPrank();
vm.startPrank(user3);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, user3);
briVault.joinEvent(2);
vm.stopPrank();
vm.startPrank(user4);
mockToken.approve(address(briVault), 10 ether);
briVault.deposit(10 ether, user4);
briVault.joinEvent(10);
vm.stopPrank();
// 7
vm.warp(block.timestamp + eventStartDate + eventEndDate);
vm.prank(owner);
briVault.setWinner(countryId);
// 8
vm.prank(alice);
briVault.withdraw();
assertEq(mockToken.balanceOf(alice), 0);
}

Recommended Mitigation

The deposit() function should mint shares to the receiver as defined by ERC4626.

function deposit(uint256 assets, address receiver) public override returns (uint256) {
(.... )
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
- _mint(msg.sender, participantShares);
+ _mint(receiver, participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}
Updates

Appeal created

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

Shares Minted to msg.sender Instead of Specified Receiver

Support

FAQs

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

Give us feedback!