BriVault

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

Duplicate usersAddress entries + overwrite of userSharesToCountry allow double-counting and inconsistent balances

Each user should appear once per event/team and contribute their shares exactly once, ensuring fair reward and accurate participant tracking.

usersAddress.push(msg.sender) runs every time without uniqueness checks, creating duplicate entries.

  • userSharesToCountry[msg.sender][countryId] overwrites instead of accumulating, causing lost or inconsistent balances.

  • numberOfParticipants++ inflates the count even for the same user


https://github.com/CodeHawks-Contests/2025-11-brivault/blob/1f515387d58149bf494dc4041b6214c2546b3b27/src/briVault.sol#L260-267

Impact

  • Users can appear multiple times in usersAddress, causing double or multiple payouts if rewards or distributions iterate over this array.

  • numberOfParticipants and totalParticipantShares are inflated, leading to incorrect accounting, misleading metrics, or unfair distribution of funds.

  • userSharesToCountry overwrites previous entries, losing old data and creating inconsistent state between user balances and total shares.

Risk


Likelihood

  • Reason 1: Each call to the join function executes usersAddress.push(msg.sender) unconditionally. Any user calling this twice will have multiple entries in the array.

  • Reason 2: Each call increments numberOfParticipants and totalParticipantShares, causing inflated totals and inconsistent bookkeeping even without malicious intent.

Impact

  • Impact 1: Loops that distribute rewards based on usersAddress will process the same user multiple times → double or multiple payouts.

  • Impact 2: numberOfParticipants and totalParticipantShares will no longer reflect real values, breaking per-user reward ratios and misleading metrics

Proof of Concept

Alice joins countryId = 1 by calling the join function.
usersAddress = [Alice]
numberOfParticipants = 1
Alice calls the same function again (intentionally or accidentally).
usersAddress = [Alice, Alice]
numberOfParticipants = 2
Later, rewards are distributed with:
for (uint i = 0; i < usersAddress.length; i++) {
address u = usersAddress[i];
rewardToken.transfer(u, rewardPerShare * userSharesToCountry[u][countryId]);
}
The loop transfers rewards twice to Alice.
Result:
Alice receives 2× the intended reward, while totals are overcounted. If this occurs repeatedly, payouts and accounting become irreversibly incorrect.
why chose this poc m ultiple calls to the same function with no re-entrancy or privileged accessproving the issue is realistic and likely.

Recommended Mitigation

using EnumerableSet for EnumerableSet.AddressSet;
EnumerableSet.AddressSet private users;
function join(uint256 countryId) external {
userToCountry[msg.sender] = teams[countryId];
uint256 participantShares = balanceOf(msg.sender);
userSharesToCountry[msg.sender][countryId] += participantShares;
users.add(msg.sender);
totalParticipantShares += participantShares;
emit joinedEvent(msg.sender, countryId);
}
Chosen mitigation: Preventing duplicate entries (via require or EnumerableSet) eliminates the root cause, ensures state consistency, and prevents gas and payout inflation.
Updates

Appeal created

bube Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Duplicate registration through `joinEvent`

Support

FAQs

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

Give us feedback!