New storage layout is incompatible: ContextUpgradeable.sol:36: Inserted `__gap`

I'm new to OpenZeppelin and I try to wrote upgradeable contracts, when I upgrade contract, I getting this error:

An unexpected error occurred:

Error: New storage layout is incompatible

@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36: Inserted `__gap`
  > New variables should be placed after all existing inherited variables

:1234: Code to reproduce

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

error GreeterError();

contract Greeter is Initializable {
    string public greeting;

    function initialize(string memory _greeting) public initializer {
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    // solhint-disable-next-line no-empty-blocks
    constructor() initializer {}

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }

    function throwError() external pure {
        revert GreeterError();
    }
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

error GreeterError();

contract GreeterV2 is Initializable, OwnableUpgradeable {
    string public greeting;

    function initialize(string memory _greeting) public initializer {
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
        __Ownable_init();
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    // solhint-disable-next-line no-empty-blocks
    constructor() initializer {}

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public onlyOwner {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }

    function throwError() external pure {
        revert GreeterError();
    }
}
import { task } from "hardhat/config";
import { TaskArguments } from "hardhat/types";
import { Greeter } from "../../types";

task("deploy:Greeter")
  .addParam("greeting", "Say hello, be nice")
  .setAction(async function (taskArguments: TaskArguments, { ethers, upgrades }) {
    const greeterFactory = await ethers.getContractFactory("Greeter");
    const greeter = <Greeter>(
      await upgrades.deployProxy(greeterFactory, [taskArguments.greeting], { initializer: "initialize" })
    );
    await greeter.deployed();
    console.log("Greeter deployed to: ", greeter.address);
  });

task("upgrade:Greeter")
  .setAction(async function (_, { ethers, upgrades }) {
    const greeterV1 = await ethers.getContractAt("Greeter", "0xXAddressX");
    const greeterV2Factory = await ethers.getContractFactory("GreeterV2");
    await upgrades.upgradeProxy(greeterV1, greeterV2Factory);
    console.log("Greeter upgraded");
  });

:computer: Environment

Hardhat: 2.8.4
@openzeppelin/contracts-upgradeable: 4.5.1

Hi @Garfield550,

When upgrading a contract, the new version must not have storage layout conflicts with the previous version. This includes storage slots used by parent contracts. There are some examples of storage layout conflicts here: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#modifying-your-contracts

In your example, the first version does not extend OwnableUpgradeable, but defines string public greeting;.

However, the second version extends OwnableUpgradeable (which extends ContextUpgradeable).

ContextUpgradable has a variable with some reserved storage uint256[50] private __gap;.
OwnableUpgradable also has address private _owner; and uint256[49] private __gap;.

Since your contract has these as parents (transitively), those variables (with 100 storage slots combined) get inserted before your own contract's string public greeting; variable, so this is a conflict.

The solution is to ensure you extend OwnableUpgradable in the first version of your contract as well, or declare enough unused storage slots in the first version if you might want to extend other contracts in the future (and then reduce the unused storage slots in the second version, corresponding to what is used by newly extended contracts).

Another fix is to write your V2 like this:

contract GreeterV1Storage {
    string public greeting;
}

contract GreeterV2 is Initializable, GreeterV1Storage, OwnableUpgradeable {
    ...
}

This ensure V1's variables are not pushed further down in storage.

2 Likes