Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Missing Storage Gap in Upgradeable Contract

Summary

The LevelOne contract, designed to be upgradeable via the UUPS proxy pattern, omits a critical safety feature: the storage gap. Without this gap, future upgrades that introduce new state variables risk overwriting existing storage slots, potentially causing silent data corruption. This violates industry best practices and undermines the safety and reliability of future upgrades.


Vulnerability Details

contract LevelOne is Initializable, UUPSUpgradeable {
address principal;
bool inSession;
uint256 schoolFees;
// ... other variables ...
IERC20 usdc;
// Missing storage gap
}

Upgradeable contracts must maintain a consistent storage layout. The Solidity compiler assigns storage slots in a deterministic manner, but any change to variable order, base contracts, or inheritance in future upgrades (e.g., LevelTwo) can shift slot allocation and result in storage collisions if new variables are added without planning.


Example Scenario

Initial Contract: LevelOne

Slot Variable
0 principal
1 inSession
... ...
N usdc

Upgraded Contract: LevelTwo

Adds:

uint256 newVariable;

If LevelTwo’s inheritance changes or if other contracts add base variables (e.g., from new abstract contracts), newVariable could be mistakenly assigned to slot N or slot 0, overwriting usdc or principal. This leads to catastrophic data corruption with no compiler warnings.


Recommended Fix

Add a reserved gap to the end of LevelOne’s storage layout:

// At the end of LevelOne
uint256[50] private __gap;

For future upgrades:

contract LevelTwo is LevelOne {
uint256 newVariable;
uint256[49] private __gap; // Shrink gap
}

This ensures new variables will occupy the reserved gap, not overlap with existing storage.

Why This Matters

  • Data Integrity: Prevents overwriting of sensitive variables like principal, studentScore, or bursary.

  • Upgrade Safety: Ensures future versions of the contract can safely add variables.

  • Protocol Longevity: Supports modular growth without forced redeployment or proxy resets.


Recommendations

  1. Immediate Fix: Add a uint256[50] private __gap to LevelOne.

  2. Documentation: Comment the gap with a warning for future developers.

  3. Tooling: Use tools like OpenZeppelin's storage layout validator or Slither's storage-vars detector before upgrades.

  4. Audit Every Upgrade: Treat upgrades as high-risk changes and audit them carefully.


Conclusion

While not an immediate exploit, the missing storage gap is a critical upgradeability oversight that opens the door to future self-inflicted bugs. Adding a storage gap is standard practice in all proxy-based upgradeable contracts, and should be remediated promptly to ensure the safety and maintainability of the protocol.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

storage collision

Support

FAQs

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