Upgradable contract and inheritance

:memo:Details

Here, my Escrow contract needs Test1 and Test2 contracts for some functionalities. Both of Test1 and Test2 needs for some reason to inherit from Can contract (could be OwnableUpgradeable from OZ upgradeable contracts). How does Solidity handle this ? Is this related to the known multiple inheritance issue when dealing with upgradable contracts ?

Code:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma experimental ABIEncoderV2;
pragma solidity 0.6.12;

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

contract Can is Initializable {
    function __Can_init() internal virtual initializer {}
}

contract Test1 is Initializable, Can {
    function __Test1_init() internal virtual initializer {
        __Can_init();
        //
    }
}

contract Test2 is Initializable, Can {
    function __Test2_init() internal virtual initializer {
        __Can_init();
        //
    }
}

contract Escrow is Initializable, Test1, Test2 {
    //
    address admin;

    //
    function initialize(address _admin) public initializer {
        __Test1_init();
        __Test2_init();
        admin = _admin;
    }
}
1 Like

Hi @sshmaxime,

Welcome to the community :wave:

Please see how this is handled in OpenZeppelin Contracts Upgradeable: https://docs.openzeppelin.com/contracts/3.x/upgradeable#multiple-inheritance

You could create unchained initializers so that you only initialize Test1, Test2 and Can once.

See the Preset as an example:

1 Like

I think I understand now. So on all the contracts - that implements a similar contract requiring call to an initializer - I need to create an init function that initialize only this contract (unchained init) and one “normal” init function that initialize parents of this contract + the unchained init. Is this correct ? If so, I updated my code accordingly to what I understood, can you let me know if it is right ?

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma experimental ABIEncoderV2;
pragma solidity 0.6.12;

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

contract Can is Initializable {
    function __Can_init() public virtual initializer {}
}

contract Test1 is Initializable, Can {
    function __Test1_init() internal virtual initializer {
        __Can_init_unchained();
    }

    function __Test1_init_unchained() internal virtual initializer {}
}

contract Test2 is Initializable, Can {
    function __Test2_init() internal virtual initializer {
        __Can_init();
    }

    function __Test2_init_unchained() internal virtual initializer {}
}

contract Escrow is Initializable, Test1, Test2 {
    //
    address admin;

    //
    function initialize(address _admin) public initializer {
        __Test1_init();
        __Test2_init_unchained();
        admin = _admin;
    }
}
1 Like

Hi @sshmaxime,

Each of your parent contracts should have an unchained init (if you are using them elsewhere you can also have an init).

Then in your child contract you can call each of the unchained init’s.

contract Escrow is Initializable, Test1, Test2 {
    //
    address admin;

    //
    function initialize(address _admin) public initializer {
        __Test1_init_unchained();
        __Test2_init_unchained();
        __Can_init_unchained();
        admin = _admin;
    }
}

Hi @abcoathup
I had a question very similar to this, but I wanted to double check as there is a slight nuance to what I am trying to do. I have logic and storage separated and storage is NOT Initializable.

abstract contract MyStorageV1 is OwnableUpgradeable, ERC20Upgradeable, ReentrancyGuardUpgradeable {
       uint public feeAmount;
}


abstract contract MyStorage is MyStorageV1 {}


contract MyLogic is MyStorage {
    function initialize(uint _feeAmount) external initializer {
       __ReentrancyGuard_init();
       __ERC20_init(tokenName, tokenSymbol);
       __Ownable_init();
       feeAmount = _feeAmount;
    }
}

I also was not 100% sure on the storage slot allocation here either. All the OZ contracts have a 50 storage slots reserved. In the current design above, would this mean that 150 slots are reserved or are the 3 upgradeable contracts sharing the top 50 slots? If they are sharing, how exactly would you upgrade one over the other?