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 19 days 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!