Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

`beatToken` address should be declared as immutable for gas optimization and security

[I-1] beatToken address should be declared as immutable for gas optimization and security

Description

The beatToken state variable in the FestivalPass contract is set once in the constructor and never modified thereafter. However, it is currently declared as a regular public state variable rather than immutable. This design misses gas optimization opportunities and doesn't enforce the intended immutability at the compiler level.

address public beatToken; // Current implementation
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender) {
setOrganizer(_organizer);
beatToken = _beatToken;
}

While there is currently no setter function to modify beatToken after deployment, declaring it as a regular state variable:

  1. Consumes unnecessary gas on every read operation (SLOAD costs ~100 gas vs immutable ~3 gas)

  2. Doesn't provide compiler-level guarantees that the address won't be changed

  3. Leaves room for potential vulnerabilities if a setter function is accidentally added in future upgrades

Impact

  • Gas Inefficiency: Each read of beatToken costs approximately 100 gas (SLOAD) instead of ~3 gas for immutable variables, affecting functions like buyPass(), attendPerformance(), and redeemMemorabilia()

  • Best Practice Violation: The variable's intended immutability is not enforced by the compiler

  • Potential Future Risk: If the contract is upgraded or modified and a setter is accidentally added, a malicious or compromised owner could change the beatToken address to:

    • Point to a malicious token contract

    • Drain rewards meant for legitimate users

    • Disrupt the entire token economy of the festival

Proof of Concept

The beatToken variable is used in multiple critical functions but is only set once:

// Set once in constructor
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender) {
beatToken = _beatToken;
}
// Used in buyPass()
function buyPass(uint256 collectionId) external payable {
// ...
BeatToken(beatToken).mint(msg.sender, bonus);
}
// Used in attendPerformance()
function attendPerformance(uint256 performanceId) external {
// ...
BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
}
// Used in redeemMemorabilia()
function redeemMemorabilia(uint256 collectionId) external {
BeatToken(beatToken).burnFrom(msg.sender, collection.priceInBeat);
}

Gas comparison:

  • Current implementation: ~2,100 gas per function call (SLOAD)

  • With immutable: ~3 gas per function call

Recommended Mitigation

Declare beatToken as immutable to ensure it cannot be modified after deployment and to optimize gas costs:

- address public beatToken;
+ address public immutable beatToken;
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender) {
setOrganizer(_organizer);
beatToken = _beatToken;
}

This change provides:

  1. Gas savings of ~97 gas per read operation

  2. Compiler-enforced immutability

  3. Clear intent that this address should never change

  4. No functional changes to the contract's behavior

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!