BriVault

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

MEDIUM-03: CountriesSet Event Emits Entire Array

Root + Impact

The CountriesSet event emits the entire 48-element string array, which costs approximately 100,000+ gas. A simple confirmation event would be sufficient.

Description

Normal behavior for gas-efficient events expects emitting only essential information needed to track state changes. Full data can be reconstructed from storage or indexed separately.

The current implementation emits all 48 team names in the event, causing massive gas costs for data that's already stored on-chain and can be queried directly.

// @> Emitting 48 strings is extremely expensive
event CountriesSet(string[48] country);
function setCountry(string[48] memory countries) public onlyOwner {
for (uint256 i = 0; i < countries.length; ++i) {
teams[i] = countries[i];
}
// @> Costs 100,000+ gas to emit
emit CountriesSet(countries);
}

Risk

Likelihood:

  • Every setCountry() call emits this expensive event

  • Called at least once per tournament

  • May be called multiple times during testing/configuration

Impact:

  • Wastes approximately 100,000+ gas per configuration

  • Significantly increases deployment and setup costs

  • Makes contract less attractive for deployers

  • Event data is redundant (already in storage)

  • Bloats event logs unnecessarily

Proof of Concept

// Current implementation
event CountriesSet(string[48] country);
emit CountriesSet(countries); // ~100,000-150,000 gas
// Event log size: 48 strings * ~20-50 bytes each = ~1000-2400 bytes
// LOG operations cost scale with data size
// Approximate cost: 8 gas per byte + overhead
// ----
// Optimized implementation
event CountriesSet(uint256 timestamp);
emit CountriesSet(block.timestamp); // ~1,500 gas
// Event log size: 32 bytes (uint256)
// Gas cost: ~375 gas for data + ~375 for topics + ~750 overhead
// SAVINGS: ~98,000-148,000 gas per call

Recommended Mitigation

Option 1: Emit minimal confirmation

-event CountriesSet(string[48] country);
+event CountriesSet(uint256 indexed timestamp, uint256 teamCount);
function setCountry(string[48] calldata countries) external onlyOwner {
+ uint256 count = 0;
for (uint256 i = 0; i < countries.length; ++i) {
+ if (bytes(countries[i]).length > 0) {
+ count++;
+ }
teams[i] = countries[i];
}
- emit CountriesSet(countries);
+ emit CountriesSet(block.timestamp, count);
}

Option 2: Emit hash for verification

-event CountriesSet(string[48] country);
+event CountriesSet(bytes32 indexed countriesHash);
function setCountry(string[48] calldata countries) external onlyOwner {
for (uint256 i = 0; i < countries.length; ++i) {
teams[i] = countries[i];
}
- emit CountriesSet(countries);
+ emit CountriesSet(keccak256(abi.encode(countries)));
}

Option 3: No event (teams readable from storage)

-event CountriesSet(string[48] country);
function setCountry(string[48] calldata countries) external onlyOwner {
for (uint256 i = 0; i < countries.length; ++i) {
teams[i] = countries[i];
}
- emit CountriesSet(countries);
+ // No event needed - teams array is public and readable
}

Recommendation: Option 1 provides good balance - confirms action with minimal data. Off-chain services can read the teams array directly if they need full team names.

Estimated Gas Savings: ~100,000-150,000 gas per setCountry() call

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!