Upgrading base and child contracts

Sir.
If A contract inherits from B contract and both contacts needs to be upgraded. Is there any rule needed to abide by to avoid A contract’s variable-layout is broken by B contract’s new variable-layout?
Thanks.

1 Like

Hi @zhengger,

Welcome to the community :wave:

Yes there is. See the following from the documentation.

You also cannot add new variables to base contracts, if the child has any variables of its own.
...
A workaround for this is to declare unused variables on base contracts that you may want to extend in the future, as a means of "reserving" those slots. Note that this trick does not involve increased gas usage.
_From: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#modifying-your-contracts_

OpenZeppelin Contracts Upgrades uses storage gaps for this purpose, see: https://docs.openzeppelin.com/contracts/3.x/upgradeable#storage_gaps

Feel free to ask all the questions that you need.

1 Like

Thank you, Sir!

I have two other questions about multiple inheritances.
I have a contract O which n inherits Ownable contract:

    contract O is Ownable {
        constructor(){
            _;
        };
}

Now I want to upgrade O to be upgradable and I made some changes as described below:

    contract O is Initializable, OwnableUpgradeable {
        function initializeO ()  initializer  {
            __Ownable_init(); 
            _;
        }
    }

// Question 1: Should I use __Ownable_init__unchained() instead of __Ownable_init() and why;
In the article you recommended it(unchained) is “used to avoid the double initialization problem” so whenever possible it’s better to use __{ContractName}_init_unchained. Did I catch it?

// Question 2: Can I omit Initializable because OwnableUpgradeable has inherited Initializable already.

Thanks!

1 Like

Hi @zhengger,

It is recommended to use __{ContractName}_init. You only need to use __{ContractName}_init_unchained where you have multiple inheritance.
See: https://docs.openzeppelin.com/contracts/3.x/upgradeable#multiple-inheritance

For clarity you should inherit Initializable as your initialize function should use the initializer modifier to ensure it is only called once.

1 Like

Thank you so much, Sir!
I have a custom struct in O contract like this:

struct UserInfo {
	bool toBePaid;
	uint256 depositAmount;
    uint256 mintedAmount;
}

And according to this link, ## Why can’t I use custom types like structs and enums?, it said that the plugins do not support such contracts. Could you teach me how to use custom types inside upgradable contracts. Here is how to use UserInfo struct in the O contract safely.
Much thanks again.

1 Like

Hi @zhengger,

It depends how you are using your struct. If you are using it with a mapping then this should be upgrade safe. If you are using it in a state variable, then you can only add to the last field in the struct if the state variable is the last state variable.

The Upgrades Plugins don’t currently support checking enums and structs for upgrade safety, see: https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#why-cant-i-use-custom-types

For checking for upgrade safety I suggest writing higher level tests that check all the state before and after an upgrade, see the proxy tests for an example: OpenZeppelin Upgrades: Step by Step Tutorial for Truffle

1 Like

Thank you, Sir!

I will write test script to check the security as you said.
This is the way I want to use it in my contract O:

contract O {

	struct UserInfo {
		bool toBePaid;
		uint256 depositAmount;
        uint256 mintedAmount;
	}
	mapping(address => UserInfo) userInfoMap;
        }

Thanks again!

1 Like

I disllike the idea of adding reserved slots because I don't know how many I may need in the future and of what types. Is it possible to inherit the child contract and add whatever new variables and feature changes into the new grandchild contract and then use the grandchild contract to upgrade the existing contract? I'm wondering if this might be a better workaround. tia!

Hi @Godwin, you don't need to know what types you will use ahead of time. You can just allocate enough slots, for example uint256[50] private __gap; and then decrease this number in later versions depending on how many slots are used up later on.

Is it possible to inherit the child contract and add whatever new variables and feature changes into the new grandchild contract and then use the grandchild contract to upgrade the existing contract?

This sounds the same as just adding to the child contract (in terms of storage layout), so the limitations would be the same.

Thanks for your reply.

Wouldn't this get messier if you want other datatypes?

What if we did something like this ChildV2 is BaseV2, BaseV2 is Child, Child is Base so that all new variables and functions can be in ChildV2 and BaseV2? I guess all functions would need to be virtual so they can be overridden, but seems better logical organization. What do you think?

Thanks.

To clarify, your other data types do not necessarily need to be uint256. The usage of uint256[50] private __gap; just reserves one full slot for each element of the array.

What if we did something like this ChildV2 is BaseV2, BaseV2 is Child, Child is Base so that all new variables and functions can be in ChildV2 and BaseV2? I guess all functions would need to be virtual so they can be overridden, but seems better logical organization. What do you think?

I don't quite understand the suggestion here. How your contracts are organized in terms of code does not affect the storage layout. If you use storage slots, the limitation remains the same that for upgrade safety, you cannot insert new variables before the existing ones. And variables in parent contracts are before variables in child contracts.

For example, I suspect that in the future I may need to add a combination of uint256, int256, string, and address. Do I just reserve 4 different arrays of each type and repeat this on all classes like so?

contract Base {
    uint256[] reserve1 = new uint256[](10);
    int256[] reserve2 = new int256[](10);
    string[] reserve3 = new string[](10);
    address[] reserve4 = new address[](10);
}

contract Child is Base {
    uint256[] reserve5 = new uint256[](10);
    int256[] reserve6 = new int256[](10);
    string[] reserve7 = new string[](10);
    address[] reserve8 = new address[](10);
}

contract BaseV2 {
    uint256 newUint = 0;
    uint256[] reserve1 = new uint256[](9);
    int256 newInt = 0;
    int256[] reserve2 = new int256[](9);
    string newString = "";
    string[] reserve3 = new string[](9);
    address newAddress;
    address[] reserve4 = new address[](9);
}

contract ChildV2 is BaseV2 {
    uint256 newUint2;
    uint256[] reserve5 = new uint256[](9);
    int256 newInt2;
    int256[] reserve6 = new int256[](9);
    string newString2;
    string[] reserve7 = new string[](9);
    address newAddress2;
    address[] reserve8 = new address[](9);
  }

I was thinking something like this:

contract Base {
    uint256 myInt;
}

contract Child is Base {
    uint256 myInt2;
}

contract BaseV2 is Child {
    string newString;
}

contract ChildV2 is BaseV2 {
    uint256 newInt;
}

Seems more elegant to me. Thoughts? Is it doable and are there any downsides? tia

Hi @Godwin,

Your example uses dynamically sized arrays which does not occupy contiguous storage slots.
Instead, use static arrays:

    uint256[10] private reserve1;
    int256[10] private reserve2;
    string[10] private reserve3;
    address[10] private reserve4;

or you could just use an array of uint256 to reserve the same number of slots (even if you will be using different types later):

    uint256[40] private reserve;

For your suggestion with chaining inheritance, that is effectively the same as the following in terms of storage slots, so still has the same limitations in terms of storage conflicts:

contract ChildV2 {
    uint256 myInt;
    uint256 myInt2;
    string newString;
    uint256 newInt;
}

Thanks for this advice.