BriVault

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

MEDIUM-02: userToCountry Stores String Instead of uint256

Root + Impact

The userToCountry mapping stores team names as strings, which is extremely gas-inefficient. Storing the uint256 country index instead would save ~15,000 gas per user on join and ~20,000 gas per withdrawal.

Description

Normal behavior for efficient storage expects using the smallest data type that can represent the needed information. Since teams are already indexed 0-47, storing the index is sufficient.

The current implementation stores the entire team name string for each user, requiring expensive string storage operations and complex string comparison using keccak256 hashing during withdrawals.

// @> Storing strings is extremely gas-inefficient
mapping(address => string) public userToCountry;
function joinEvent(uint256 countryId) public {
// ... validation ...
// @> Stores full string: "TeamName" costs ~20,000 gas
userToCountry[msg.sender] = teams[countryId];
// ... rest ...
}
function withdraw() external winnerSet {
// ... validation ...
// @> Expensive string comparison with double keccak256
if (
keccak256(abi.encodePacked(userToCountry[msg.sender])) !=
keccak256(abi.encodePacked(winner))
) {
revert didNotWin();
}
// ... rest ...
}

Risk

Likelihood:

  • Every user calling joinEvent() incurs string storage cost

  • Every winner calling withdraw() incurs string comparison cost

  • With thousands of users, costs compound to millions of gas wasted

Impact:

  • Each user wastes ~15,000 extra gas joining event

  • Each winner wastes ~20,000 extra gas on withdrawal

  • Protocol becomes significantly more expensive than necessary

  • Poor user experience due to high transaction costs

  • Makes protocol less competitive vs alternatives

  • Limits adoption due to cost barriers

Proof of Concept

// Current implementation - string storage
mapping(address => string) public userToCountry;
// joinEvent cost breakdown:
// - SSTORE full string "TeamName": ~20,000 gas
// - String copying operations: ~5,000 gas
// Total extra cost: ~25,000 gas
// withdraw cost breakdown:
// - SLOAD full string: ~2,100 gas
// - keccak256(userString): ~5,000 gas
// - keccak256(winner): ~5,000 gas
// - Comparison: ~100 gas
// Total extra cost: ~12,000 gas
// ----
// Optimized implementation - uint256 storage
mapping(address => uint256) public userToCountryId;
// joinEvent cost breakdown:
// - SSTORE uint256: ~5,000 gas
// Total extra cost: ~5,000 gas
// SAVED: ~20,000 gas
// withdraw cost breakdown:
// - SLOAD uint256: ~2,100 gas
// - uint256 comparison: ~3 gas
// Total extra cost: ~2,100 gas
// SAVED: ~10,000 gas per withdrawal

Recommended Mitigation

-mapping(address => string) public userToCountry;
+mapping(address => uint256) public userToCountryId;
function joinEvent(uint256 countryId) public {
// ... validation ...
- userToCountry[msg.sender] = teams[countryId];
+ userToCountryId[msg.sender] = countryId;
uint256 participantShares = balanceOf(msg.sender);
userSharesToCountry[msg.sender][countryId] = participantShares;
// ... rest ...
}
function withdraw() external winnerSet {
if (block.timestamp < eventEndDate) {
revert eventNotEnded();
}
- if (
- keccak256(abi.encodePacked(userToCountry[msg.sender])) !=
- keccak256(abi.encodePacked(winner))
- ) {
- revert didNotWin();
- }
+ if (userToCountryId[msg.sender] != winnerCountryId) {
+ revert didNotWin();
+ }
// ... rest of withdrawal logic ...
}
+// Optional: Add view function to get team name
+function getUserTeamName(address user) external view returns (string memory) {
+ uint256 countryId = userToCountryId[user];
+ return teams[countryId];
+}

Estimated Gas Savings:

  • ~15,000 gas per joinEvent() call

  • ~20,000 gas per withdraw() call

  • With 1000 users: saves ~15,000,000 gas on joins + variable on withdrawals

Updates

Appeal created

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

Gas optimizations

Gas optimizations are invalid according to the CodeHawks documentation.

Support

FAQs

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

Give us feedback!