ERC721Upgradeable multiple upgrades

HI

I’m Practicing with the repository on Github that gives the next smart contracts…

@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol

I follow the tutorial (great tutorial!!!) https://docs.openzeppelin.com/learn/upgrading-smart-contracts

But i cant migrate from the second to third smart contract…

:computer: Environment
I’m using Truffle, Ganache :slight_smile:

:memo:Details
The information that i created in the second one, i don’t know, how can i persist? Let me explain for you…

1.- I have Main with var
2.- I create a MainV2 with new Var
3.- Which Main need to point mainV3, for take all Main and MainV2 Information…

await MainV3.at(Main.address) or await MainV3.at(MainV2.address)

:1234: Code to reproduce
I tried on both ways :frowning:

My migrations files… for the second level…


(I tried to upload all my code :frowning: but if you need more explanation, i can try to explain more…)

Thanks in advance… sorry for my bad english :slight_smile:

I hope someone could teach me about this…

1 Like

Hi @SrChong,

Welcome to the community :wave:

When upgrading using the Upgrades Plugins, we need to use the original version to get the address of the proxy contract.

So it should be MainV1.deployed() to get the address.

Oh, i understand.

So. We can’t add new variables to persist the created in the second one, in the third, and 4th level.

Because if i added storage in the second, and the address is from the first. In the third upgrade i lost the variables crated of the second level… at least… i can use many proxys from the first contract based in functions to access like ethernal storage concept.

Excelent. I suppose that i need to think diferent now… my questions is because in one exampe, says with Zeppelin Cli, you can persist storage from second level and first level in third level. I think this is a mistake.

Thanks.

1 Like

Hi @SrChong,

You can definitely upgrade multiple times, as long as you don’t change the order or type of state variables. You can add new state variables in each upgrade.

In Upgrades Plugins, we just use the original contract to get the address of the proxy contract. This doesn’t limit how many times you can upgrade.

I recommend trying it out yourself with a simple contract.

Hi @SrChong,

I wanted to check that you were able to do multiple upgrades?

Hi abcoathup, thanks!

I try several times, but i have an error when i tried to call the function that i created in V2 contract… in v4 (real v3, but i skip the number, sorry)

Also it happened if i write wrong the name of the function…

Garnache is like printing something about numbers blocked, and i can’t call nothing again until restart garnache…

I thoght that is an error of variables type, but the function works fine in v2…

1 Like

Hi @SrChong,

I am sorry you are still having issues.

I created the following example upgrading Box -> BoxV2 -> BoxV3

Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
 
contract Box {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }

    function version() public pure virtual returns (string memory) {
        return "v1.0";
    }
}

BoxV2.sol

// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
 
import "./Box.sol"; 

contract BoxV2 is Box {
    function version() public pure virtual override returns (string memory) {
        return "v2.0";
    }
}

BoxV3.sol

// contracts/BoxV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
 
import "./Box.sol"; 

contract BoxV3 is Box {
    function version() public pure virtual override returns (string memory) {
        return "v3.0";
    }
}

2_deploy_box.js

// migrations/2_deploy_box.js
const Box = artifacts.require('Box');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  await deployProxy(Box, [42], { deployer, initializer: 'store' });
};

3_upgrade_boxv2.js

// migrations/3_upgrade_boxv2.js
const { upgradeProxy } = require('@openzeppelin/truffle-upgrades');

const Box = artifacts.require('Box');
const BoxV2 = artifacts.require('BoxV2');

module.exports = async function (deployer) {
  const box = await Box.deployed();
  const boxV2 = await upgradeProxy(box.address, BoxV2, { deployer });
  console.log("Upgraded", boxV2.address);
};

4_upgrade_boxv3.js

// migrations/4_upgrade_boxv3.js
const { upgradeProxy } = require('@openzeppelin/truffle-upgrades');

const Box = artifacts.require('Box');
const BoxV3 = artifacts.require('BoxV3');

module.exports = async function (deployer) {
  const box = await Box.deployed();
  const boxV3 = await upgradeProxy(box.address, BoxV3, { deployer });
  console.log("Upgraded", boxV3.address);
};

Migrate and interact

$ npx truffle develop
Truffle Develop started at http://127.0.0.1:9545/

...

truffle(develop)> migrate

Compiling your contracts...
===========================
...

Starting migrations...
======================
> Network name:    'develop'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================
...


2_deploy_box.js
===============

   Deploying 'Box'
   ---------------
...
   Deploying 'ProxyAdmin'
   ----------------------
...
   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
...

3_deploy_boxv2.js
=================

   Deploying 'BoxV2'
   -----------------
...

Upgraded 0xB715B0DAF6097Be133A1E09AF21a84719582E720
...

4_deploy_boxv3.js
=================

   Deploying 'BoxV3'
   -----------------
...

Upgraded 0xB715B0DAF6097Be133A1E09AF21a84719582E720

...

truffle(develop)> box = await Box.deployed()
undefined
truffle(develop)> box.version()
'v3.0'

Hi @SrChong,

Were you able to try the above?

Hi :slight_smile:

Give me some hours to try, i was into covid issues.

Thanks for the information, i will give you a retro soon.

1 Like

I tried the code, but i have some doubts… Let me take your example and modify a little… and ask for some question please…

BoxV1.sol

// contracts/BoxV1.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
 
contract BoxV1 {
    uint256 private value;
    PetCHG[] pets;
    struct PetCHG {
        uint64 birthTime;
        string dna;
    }
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }

    function version() public pure virtual returns (string memory) {
        return "v1.0";
    }

    function doMint(string memory _dna) public virtual {
        
        PetCHG memory _pet = PetCHG({ dna: _dna, birthTime: uint64(block.timestamp)});
        
        pets.push(_pet);
    }

    function getPetID( uint _petId ) public view returns(string memory dna){
        PetCHG memory _pet = pets[_petId];

        dna = _pet.dna;
    }
}

BoxV2.sol

1.- Here i create a new variable… but in BoxV3 i can’t call this one, i need to extend also from BoxV2? and if yes, how deploy file needs to be?
2.- I comment a structure, i know that is not fully supported, but that is possible to add variables inside an struct?

// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
 
import "./BoxV1.sol"; 

contract BoxV2 is BoxV1 {

    uint256 private valueV2;
    /*
    struct PetCHG {
        uint64 birthTime;
        string dna;
    }

    function doMint(string memory _dna) public virtual override {
        string memory myDna = string(abi.encodePacked(_dna, " chong"));
        PetCHG memory _pet = PetCHG({ dna: myDna , birthTime: uint64(block.timestamp)});
        pets.push(_pet);

       
    }*/

    function version() public pure virtual override returns (string memory) {
        return "v2.0";
    }

    function storeV2(uint256 newValue) public {
        valueV2 = newValue;
        emit ValueChanged(newValue);
    }

    function retrieveV2() public view returns (uint256) {
        return valueV2;
    }
}

BoxV3.sol

// contracts/BoxV3.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
 
import "./BoxV1.sol";

contract BoxV3 is BoxV1 {

    function doMint(string memory _dna) public virtual override {
        string memory myDna = string(abi.encodePacked(_dna, "chong"));
        PetCHG memory _pet = PetCHG({ dna: myDna , birthTime: uint64(block.timestamp)});
        pets.push(_pet);

    }

    function version() public pure virtual override returns (string memory) {
        return "v3.0";
    }
}

also, since first execution of my struct en BoxV1.doMint() i obtain a several console log of the next:

eth_getBlockByNumber
eth_getBlockByNumber

i read that is because in the struct doesn’t create a valid variable, but it’s only two… or it is because it’s not supported?

Thanks for all your comments…

New implementation contracts don't have to extend from previous versions. They just need variables to be in the same order and have the same type.

If we want to use inheritance we can inherit from the previous version.

// contracts/BoxV3.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
 
import "./BoxV2.sol";

contract BoxV3 is BoxV2 {

    function doMint(string memory _dna) public virtual override {
        string memory myDna = string(abi.encodePacked(_dna, "chong"));
        PetCHG memory _pet = PetCHG({ dna: myDna , birthTime: uint64(block.timestamp)});
        pets.push(_pet);

    }

    function version() public pure virtual override returns (string memory) {
        return "v3.0";
    }
}

The migration script looks as follows.
We use BoxV1 to get the proxy address

// migrations/4_upgrade_boxv3.js
const { upgradeProxy } = require('@openzeppelin/truffle-upgrades');

const BoxV1 = artifacts.require('BoxV1');
const BoxV3 = artifacts.require('BoxV3');

module.exports = async function (deployer) {
  const boxV1 = await BoxV1.deployed();
  const boxV3 = await upgradeProxy(boxV1.address, BoxV3, { deployer, unsafeAllowCustomTypes: true });
  console.log("Upgraded", boxV3.address);
};

It depends how the struct is being used, whether it is a state variable or being used inside a mapping.

If you have a contract state variable of the struct type, then you most likely won’t be able to add any fields (unless the variable is the last one on your contract).

If the struct is contained in a mapping, then you probably should be able to add fields, since structs will be stored sparsely in storage.

I am not sure about how arrays are stored, so I assume you won't be able to add any fields to your struct.

Though you should do automated testing for upgrade safety to ensure that state is maintained across upgrades.

I am not sure why that is. I was just using Truffle Develop.

truffle(develop)> const boxV1 = await BoxV1.deployed()
undefined
truffle(develop)> const boxV2 = await BoxV2.at(boxV1.address)
undefined
truffle(develop)> const boxV3 = await BoxV3.at(boxV1.address)
undefined
truffle(develop)> boxV3.doMint('123')
{ tx:
...

Hi

I'll replicate all with inheritance, it's work :slight_smile: thanks a lot.

Last Question about your comment:

And according to the next page writing-upgradeable if i only deploy BoxV1 twice, the second time i deployed, i only need leave the variables, remove all new functions, and user override or write new functions...

I'm happy from this, thanks for all your help.
whatever the answer is, i think we could close as solution.

Thanks

1 Like

Hi @SrChong,

It is not upgrade safe to add fields to a struct being used in an array.

Implementation contracts can be stand alone or they can inherit from previous versions of the implementation contract. It is up to you for what works for your use case.

I tend to use stand alone. I only used inheritance as an example here.

OpenZeppelin Upgrades: Step by Step Tutorial for Truffle shows an example of implementation contracts not inheriting from previous versions.