No validation error thrown when adding state variable to storage in the wrong position on upgraded implementation contract

I'm trying to upgrade proxy contracts that use an inherited storage pattern - that is, the order of the state variables cannot change.

In v2 of my implementation contract, I added a new state variable to the storage in a position that should cause a storage collision (i.e. it was not appended to the end).

Then i called prepareUpgade():

const v2 = await ethers.getContractFactory("JCollateralCapErc20Delegate");
const v2Address = await upgrades.prepareUpgrade(proxyAddress, v2);

And to my surprise, it did not throw any validation error.

Is there something I'm missing here? Do i need to call prepareUpgrade() with some flag to check of changes in storage layout. I looked in the docs but couldn't find anything of the sort.

I think this way will deploy a new implementation contract rather than deploy and replace, you can have a look at this for more detail: OpenZeppelin Hardhat Upgrades API - OpenZeppelin Docs

Validates and deploys a new implementation contract, and returns its address. 
Use this method to prepare an upgrade to be run from an admin address you 
do not control directly or cannot use from Hardhat.

And I think if you want to upgrade your contract with a new implementation, here is an example:

const { ethers, upgrades } = require("hardhat");

async function main() {
  // Deploying
  const Box = await ethers.getContractFactory("Box");
  const instance = await upgrades.deployProxy(Box, [42]);
  await instance.deployed();

  // Upgrading
  const BoxV2 = await ethers.getContractFactory("BoxV2");
  const upgraded = await upgrades.upgradeProxy(instance.address, BoxV2);
}

main();

Hey. I did that but it still didn't throw an error:

const { ethers, upgrades } = require("hardhat");

// This the old implementation artifact
const DELEGATE_ARTIFACT = require("../deployments/rinkeby/JUsdcDelegate.json");

async function main() {
  // Deploying
  const v1 = await ethers.getContractFactory(
    DELEGATE_ARTIFACT.abi,
    DELEGATE_ARTIFACT.bytecode
  );
  const instance = await upgrades.deployProxy(v1, { initializer: false });
  await instance.deployed();

  // Upgrading
  const v2 = await ethers.getContractFactory("JCollateralCapErc20Delegate");
  const upgraded = await upgrades.upgradeProxy(instance.address, v2);
}

main();

Can you please share the Solidity code so we can see if there is actually an error?

Sure.

V1: https://gist.github.com/cryptofish7/df06b11d0dcf1be030cd5b744486b86c
V2: https://gist.github.com/cryptofish7/a194bc4ed8229a16434c24cf6e92a960

You know what, I just tested it in a separate project and it throws the error I'm looking for. I think previously it wasn't because I wasn't versioning it properly since the changes I made are in-place (as opposed to creating a new contract).

In the tutorial, it doesn't have this issue since the first contract is called Box and the upgraded one is BoxV2. What if the upgrade was made to Box? Do you have any tips here on how to best version upgrades?

1 Like

We generally recommend keeping both versions side by side, as in Box (aka BoxV1) and BoxV2, because that allows you to test the upgrade more easily.