F-2026-0002·storage-layout-mismatch

Incorrect storage slot annotations create high-risk reference hazard for V2 upgrade

Fixedvaultleveragedprediction-marketgithub.com/bloom-art/dripster-lend
TL;DR

All 28 inline slot annotations are off by 4 because they assume OpenZeppelin v4 sequential base storage rather than v5 ERC-7201 namespaced storage, creating a V2 upgrade hazard where a developer trusting the comments could overwrite globalAdmin.

Severity
MEDIUM
Impact
HIGH
Likelihood
LOW
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

Description

The storage layout section contains inline comments documenting the EVM storage slot for every state variable. All 28 slot annotations are systematically incorrect, every comment is off by exactly 4 slots.

The comments were written assuming OpenZeppelin v4 behavior, where UUPSUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, and EIP712Upgradeable each reserved sequential slots (0 to 3). OZ v5 replaced this with ERC-7201 namespaced storage, where each base contract stores its state at a deterministic hash-derived location rather than in sequential slots. This means the contract's own variables begin at slot 0, not slot 4.

solidity
// Note: UUPSUpgradeable uses slots 0-1, ReentrancyGuard slot 2, Pausable slot 3
// (INCORRECT) OZ v5 base contracts use ERC-7201 hashed storage, not slots 0-3
// Admin addresses (slots 4-7)
address public globalAdmin; // slot 4 (actual: slot 0)
address public emergencyAdmin; // slot 5 (actual: slot 1)
address public appAdmin1; // slot 6 (actual: slot 2)
address public appAdmin2; // slot 7 (actual: slot 3)
// External contracts (slots 8-10)
address public usdcToken; // slot 8 (actual: slot 4)
address public conditionalTokens; // slot 9 (actual: slot 5)
address public capitalPool; // slot 10 (actual: slot 6)
// ... (pattern continues for all 28 annotated variables)
// Storage gap for upgrades (slots 32-65)
uint256[34] private _gap; // (gap actually starts at slot 24, ends at slot 57)

There is no runtime impact on V1. The actual storage layout is correct, the gap is correctly sized, and no contract logic depends on slot number comments. The risk is entirely in the upgrade path.

Vulnerable scenario:

  1. A developer begins work on V2 and reads the storage layout comments to understand which slots are occupied.
  2. The comments say slots 0 to 3 are reserved for OZ base contracts, the developer believes these are "OZ-reserved" and safe to use for V2 variables.
  3. The developer places a new V2 variable at slot 0 (thinking it's free), but globalAdmin is already there.
  4. After a UUPS upgradeToAndCall, the new variable overwrites globalAdmin in the proxy's storage, corrupting protocol governance and potentially granting control to an attacker or permanently disabling admin access.

PoC (verified with forge test):

solidity
function test_globalAdmin_at_slot_0_not_slot_4() public view {
bytes32 slot0Value = vm.load(address(vault), bytes32(uint256(0)));
address globalAdminFromSlot0 = address(uint160(uint256(slot0Value)));
assertEq(globalAdminFromSlot0, globalAdmin);
}
function test_verify_sequential_admin_slots_from_zero() public view {
bytes32 slot0 = vm.load(address(vault), bytes32(uint256(0)));
bytes32 slot1 = vm.load(address(vault), bytes32(uint256(1)));
bytes32 slot2 = vm.load(address(vault), bytes32(uint256(2)));
bytes32 slot3 = vm.load(address(vault), bytes32(uint256(3)));
assertEq(address(uint160(uint256(slot0))), globalAdmin); // slot 0, not 4
assertEq(address(uint160(uint256(slot1))), emergencyAdmin); // slot 1, not 5
assertEq(address(uint160(uint256(slot2))), appAdmin1); // slot 2, not 6
assertEq(address(uint160(uint256(slot3))), appAdmin2); // slot 3, not 7
}
03Section · Impact

Impact

A V2 developer relying on these comments to plan new storage additions could overwrite the four admin address slots (slots 0 to 3), corrupting globalAdmin, emergencyAdmin, appAdmin1, or appAdmin2 in a live upgrade. The _gap range annotation (slots 32-65) is also incorrect (actual: slots 24-57), potentially misleading V2 capacity planning.

04Section · Recommendation

Recommendation

Update the storage section header comment and all 28 inline slot annotations to reflect the actual OZ v5 ERC-7201 layout (apply minus-4 correction to all slot numbers).

Add forge inspect LeveragedPredictionVaultV1 storage-layout to the upgrade checklist as a mandatory pre-deployment verification step to prevent future comment drift.

05Section · Resolution

Resolution

Fixed. Slot annotations corrected; OpenZeppelin Foundry-upgrades plugin validates layout in CI.

Status
Fixed
F-2026-0002

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx