Root + Impact
The deposit() function mints shares to msg.sender but updates stakedAsset[receiver]. This mismatch allows an attacker to inflate their shares by repeatedly depositing for different receivers and canceling. Since cancelParticipation() only checks stakedAsset[msg.sender] and burns balanceOf(msg.sender), an attacker can deposit for address(1), receive shares, then cancel (which fails the refund but burns 0 shares since stakedAsset[msg.sender] = 0), keeping the minted shares. This breaks the share accounting system.
Impact
Attackers can mint unlimited shares without corresponding asset deposits, diluting legitimate participants' shares and potentially claiming the entire vault balance as a “winner” with inflated shares.
Scenario
vault.deposit(1000 ether, address(0xdead));
vault.cancelParticipation();
vault.deposit(100 ether, attacker);
vault.deposit(1000 ether, address(0xdead));
vault.joinEvent(winningCountry);
Affected code
function deposit(uint256 assets, address receiver) public override returns (uint256) {
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;
}
Proposed fix
function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
require(receiver == msg.sender, "Can only deposit for self");
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(receiver, participantShares);
emit deposited(receiver, stakeAsset);
return participantShares;
}