BriVault

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

CRITICAL-04: Deposit Function Receiver/Minter Mismatch Creates Security Risk

Root + Impact

The deposit() function tracks staked assets for the receiver parameter but mints shares to msg.sender, creating a dangerous mismatch that allows front-running and gaming of the system.

Description

Normal behavior in ERC4626 vaults expects consistent handling where if shares are minted to an address, that same address should be tracked for deposits and withdrawals.

The current implementation assigns stakedAsset[receiver] but calls _mint(msg.sender, shares), creating a split where one address holds the shares but another address is tracked as the depositor. This breaks the tracking system and enables manipulation.

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;
// @> Tracks deposit under 'receiver'
stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
IERC20(asset()).safeTransferFrom(
msg.sender,
participationFeeAddress,
fee
);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
// @> But mints shares to 'msg.sender' - MISMATCH
_mint(msg.sender, participantShares);
emit deposited(receiver, stakeAsset);
return participantShares;
}

Risk

Likelihood:

  • Function interface explicitly allows receiver != msg.sender with no validation preventing this

  • Attackers can call deposit with arbitrary receiver addresses

  • Common pattern in DeFi to have separate receiver parameters, making this appear intentional

Impact:

  • Alice can call deposit(1000, bob) - Bob's stakedAsset increases but Alice receives the shares

  • When Bob checks joinEvent(), it passes because stakedAsset[bob] > 0

  • Bob can join event but has zero shares - receives nothing on winning

  • Alice holds shares without being tracked as participant - can potentially bypass restrictions

  • In cancelParticipation(), Bob gets refund based on stakedAsset[bob] but Alice holds the shares

  • Creates accounting mismatch between share holders and stake trackers

  • Enables griefing attacks where attacker deposits "for" victims who then can't claim

  • Breaks the fundamental assumption that depositor = share holder

Proof of Concept

// Alice deposits but specifies Bob as receiver
alice.deposit(1000e18, bob);
// Result:
// - stakedAsset[bob] = 990 (Bob tracked as depositor)
// - balanceOf(alice) = 990 shares (Alice owns shares)
// - balanceOf(bob) = 0 (Bob has no shares)
// Bob can join event (passes check: stakedAsset[bob] > 0)
bob.joinEvent(0);
// After winner is set:
// - Bob tries to withdraw: FAILS (no shares)
// - Alice tries to withdraw: FAILS (didn't join event)
// - Funds are LOCKED permanently

Recommended Mitigation

function deposit(
uint256 assets,
address receiver
) public override returns (uint256) {
require(receiver != address(0));
+ require(receiver == msg.sender, "Receiver must be msg.sender");
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;
}
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!