Skip to content

Storage layout

When using delegatecall, the implementation contract’s code is executed in the proxy’s storage context. For that to work properly, the storage layout in the proxy must match the storage layout in the implementation.

However, in your StorageProxy, you’ve inherited from OpenZeppelin’s Ownable, which adds its own storage variables—specifically _owner. This shifts around your storage slots in a way that your ImplementationvX contracts do not account for.

Parent contract

The parent contract extends the Ownable contract from Openzeplin

1. OpenZeppelin’s Ownable

In the standard (simplified) OpenZeppelin Ownable contract, you typically have something like:

abstract contract Ownable {
address private _owner;
// ...
}

So, for Ownable, the first storage slot (slot 0) is _owner.


2. StorageProxy

Your StorageProxy contract inherits from Ownable and declares two variables:

contract StorageProxy is Ownable {
uint public num; // (1)
address implementation; // (2)
...
}

So the layout in StorageProxy is effectively:

  • Slot 0_owner
  • Slot 1num
  • Slot 2implementation

3. Implementation v1 / v2 / v3

Each of your implementation contracts declares:

solidity
Copy code
contract ImplementationvX {
uint public num; // Only variable
...
}

Because there’s just one variable (uint public num), it sits in slot 0 in each of those contracts:

  • Slot 0num

Storage layout mismatch

Screenshot 2025-01-03 at 7.43.11 PM.png