Storage Gap for abstract contract

How do you implement storage gap for abstract inherited contracts?

Do I need to inherit upgradable OZ contracts?
Do I need storage gap for each abstract contract?
Do I need storage gap for contract?
Do I need to change the storage gap for contract to__gap[48]?
Since contractB inherit contractA gap, does this mean that contractB has 100 gap?
Since contracC inherit contractB gap, does this mean that contractC has 100 gap?
Since I'm using immutable for contractA, does this means that I do not need to update the storage gap?

Aside from storage gap, apparently, the contract is still upgradable, I tested it with admin.upgrade(proxy, address(contract)); How is this possible?

:1234: Code to reproduce

This is my implementation.

abstract contractA is
    Initializable,
    ContextUpgradeable,
    
{
    using AddressUpgradeable for address;

    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address public immutable protocol;
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address public immutable registry;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address _protocolAddress, address _RegistryAddress) {
        protocol= _protocolAddress;
        registry= _RegistryAddress;
    }
  uint256[50] private __gap;
  -- SNIP--
}
abstract contract contractB is contractA {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address _protocolAddress, address _RegistryAddress)
        contractA(_protocolAddress, _RegistryAddress)
    {}
    uint256[50] private __gap;
}
contract contractC is contractB {

    constructor(address _protocolAddress, address _Registry)
        contractB(_protocolAddress, _Registry)
    {}

How do you implement storage gap for abstract inherited contracts?

For more info on storage gaps, see https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps

For real examples, see https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/tree/v4.9.6/contracts

Do I need to inherit upgradable OZ contracts?

That is recommended, but you still need to manage either gaps or namespaced storage in your own contracts if you want to be able to make changes to your contracts during upgrades if they are inherited by another contract.

Do I need storage gap for each abstract contract?
Do I need storage gap for contract?

Same as above.

Do I need to change the storage gap for contract to__gap[48]?

Keeping a consistent number of total slots for each contract makes it easier to track.

Since contractB inherit contractA gap, does this mean that contractB has 100 gap?

Yes. But if you add variables in contractB before the gap, then the gaps would not be contiguous.

Since contracC inherit contractB gap, does this mean that contractC has 100 gap?

Yes.

Since I'm using immutable for contractA, does this means that I do not need to update the storage gap?

Immutable variables do not consume storage slots.

Aside from storage gap, apparently, the contract is still upgradable, I tested it with admin.upgrade(proxy, address(contract)); How is this possible?

How are you performing the upgrade? If you are upgrading via the contract directly, then it will let you upgrade even if it's unsafe! You should use the Upgrades Plugins which checks for storage layout compatibility.

2 Likes

For each contract that implement __gap[50] it is only affects that particular contract right? If that contract were to add a new variable you have to reduce to 49 then upgrade it, am I right?

So if contractB was to inherit the new contract, does it need to reduce the gap too?

How did contractC have 100 gap when you didn't really increment it though?

Since contractA and contractB has implement a gap does contractC require to implement a gap? Or it will be known that since it inherit it will automatically have 100 gap

Lastly, why is the contract upgradeable when I didn't inherit a ownable upgradeble? or is it because of the TransparentUpgradeableProxy deployed? and the contractB is the implementation hence able to call .upgrade?

For each contract that implement __gap[50] it is only affects that particular contract right?

It affects that contract and all contracts which inherit it.

If that contract were to add a new variable you have to reduce to 49 then upgrade it, am I right?

Generally yes, but keep in mind that your variables may be packed tightly depending on their type. See https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html

So if contractB was to inherit the new contract, does it need to reduce the gap too?

If contractB did not inherit a specific contract before, then inherits it for an upgrade, then yes it needs to reduce its gap corresponding to the amount of storage slots used by variables in the newly inherited contract.

How did contractC have 100 gap when you didn't really increment it though?

C inherits B inherits A. B and A each use 50 slots.

Since contractA and contractB has implement a gap does contractC require to implement a gap? Or it will be known that since it inherit it will automatically have 100 gap

If there might be another contract D that will inherit C, then it would be useful to also have a gap in C if you want the flexibility to add variables in C later.

Lastly, why is the contract upgradeable when I didn't inherit a ownable upgradeble? or is it because of the TransparentUpgradeableProxy deployed? and the contractB is the implementation hence able to call .upgrade?

If you are using TransparentUpgradeableProxy, then the owner of the ProxyAdmin can upgrade the proxy.