BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Atomicity Gap — Separate deposit() and joinEvent() Can Permanently Lock Funds

High Finding (F-002)

Title: Atomicity Gap — Separate deposit() and joinEvent() Can Permanently Lock Funds

  • Impact: A user who deposits just before event start but fails to call joinEvent() becomes a partial participant: unable to use standard redeem (per F‑001 mitigation) and unable to withdraw via winner-only path — funds become irrecoverable.

  • Evidence: deposit() mints shares and records stakedAsset, while joinEvent() sets userToCountry; both are time‑gated and not atomic.

  • Recommendation (formal mitigation):

    1. Merge deposit and joinEvent into a single atomic call (e.g., stakeAndChooseCountry) that performs transfer, fee deduction, share minting, and country registration in one transaction.

    2. If maintaining separate functions, provide a safe recovery path: allow a limited-time manual claim or admin-assisted refund for deposits that were not followed by joinEvent() before eventStartDate (audit the threat of admin abuse if chosen).

    3. Add tests covering edge timing scenarios and revert messages for clarity.

Illustrative API:

function stakeAndChooseCountry(uint256 assets, uint256 countryId) external returns (uint256 shares) {
if (block.timestamp >= eventStartDate) revert eventStarted();
require(countryId < teams.length, "Invalid country index");
uint256 fee = _getParticipationFee(assets);
require(assets > fee && assets - fee >= minimumAmount, "Insufficient amount after fee");
uint256 stakeAsset = assets - fee;
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
shares = _convertToShares(stakeAsset);
_mint(msg.sender, shares);
stakedAsset[msg.sender] += stakeAsset;
userToCountry[msg.sender] = teams[countryId];
userSharesToCountry[msg.sender][countryId] += shares;
if (!hasJoined[msg.sender]) {
hasJoined[msg.sender] = true;
usersAddress.push(msg.sender);
}
emit joinedEvent(msg.sender, countryId);
return shares;
}

Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!