The deposit() function keeps track of stakedAsset mapping using the parameter receiver, and user won't be able to join the event due to the checks
Description
The `deposit()` function tracks staked assets to the `receiver` address but mints shares to `msg.sender`. This creates a critical mismatch that prevents users from joining events. When a user deposits, their shares are minted to their address, but if `receiver != msg.sender`, the `stakedAsset` mapping is updated for the wrong address. Since `joinEvent()` checks `stakedAsset[msg.sender]`, users who deposited will be unable to join events.
@>function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
uint256 stakeAsset = assets - fee;
@> stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
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;
}
function joinEvent(uint256 countryId) public {
@> if (stakedAsset[msg.sender] == 0) {
revert noDeposit();
}
Risk
Likelihood:
Impact:
-
Users who call deposit(amount, differentAddress) (even accidentally) will have shares but cannot join events, resulting in locked funds
-
Users lose their participation fee (1.5%) permanently without gaining any event participation rights
-
Breaks core protocol functionality where deposits should automatically grant event participation rights
Proof of Concept
add this function to your test suite:
function test_userCantJoin() public {
vm.prank(owner);
briVault.setCountry(countries);
uint256 deposit = 0.1 ether;
vm.startPrank(user1);
mockToken.approve(address(briVault), deposit);
briVault.deposit(deposit, user2);
assert(briVault.stakedAsset(user1) != deposit);
assertEq(briVault.balanceOf(user1), 0);
assertEq(briVault.stakedAsset(user1), 0);
assertEq(briVault.stakedAsset(user2), 0);
assertEq(briVault.balanceOf(user2), 0);
briVault.joinEvent(1);
vm.expectRevert(BriVault.noDeposit.selector);
vm.stopPrank();
vm.startPrank(user2);
vm.expectRevert(BriVault.noDeposit.selector);
briVault.joinEvent(1);
vm.stopPrank();
}
Recommended Mitigation
Option 1: Track stakedAsset for msg.sender
function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
uint256 stakeAsset = assets - fee;
- stakedAsset[receiver] = stakeAsset;
+ stakedAsset[msg.sender] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
_mint(msg.sender, participantShares);
- emit deposited(receiver, stakeAsset);
+ emit deposited(msg.sender, stakeAsset);
return participantShares;
}
Option 2: Remove receiver parameter entirely
- function deposit(uint256 assets, address receiver) public override returns (uint256) {
+ function deposit(uint256 assets) public returns (uint256) {
+ address receiver = msg.sender;
require(receiver != address(0));