1

I know there’s a similar question, but it doesn’t address the case of multi-level inheritance. I even commented on the answer, and the original respondent (i.e., Lauri Peltonen) recommended that I post a new question. That’s why I’m posting this one.

So, in the case of multi-level inheritance (A -> B -> C), for the contract that is in the middle of the hierarchy (i.e., B in this case), should it use onlyInitializing or initializer?

I mean, should it be like this:

contract A is Initializable {
    function __A_init() onlyInitializing {}
}

contract B is Initializable, A {
    function __B_init() initializer {
          __A_init();
    }
}

contract C is Initializable, B {
    function __C_init() initializer {
          __B_init();
    }
}

Or, this?

contract A is Initializable {
    function __A_init() onlyInitializing {}
}

contract B is Initializable, A {
    function __B_init() onlyInitializing {
          __A_init();
    }
}

contract C is Initializable, B {
    function __C_init() initializer {
          __B_init();
    }
}

1 Answer 1

2

The first one will fail,

If you see the initializer modifier:

modifier initializer() {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    // Cache values to avoid duplicated sloads
    bool isTopLevelCall = !$._initializing;
    uint64 initialized = $._initialized;

    // Allowed calls:
    // - initialSetup: the contract is not in the initializing state and no previous version was
    //                 initialized
    // - construction: the contract is initialized at version 1 (no reininitialization) and the
    //                 current contract is just being deployed
    bool initialSetup = initialized == 0 && isTopLevelCall;
    bool construction = initialized == 1 && address(this).code.length == 0;

    if (!initialSetup && !construction) {
        revert InvalidInitialization();
    }
    $._initialized = 1;
    if (isTopLevelCall) {
        $._initializing = true;
    }
    _;
    if (isTopLevelCall) {
        $._initializing = false;
        emit Initialized(1);
    }
}

Notice the part

    if (isTopLevelCall) {
        $._initializing = true;
    }
    _;
    if (isTopLevelCall) {
        $._initializing = false;
        emit Initialized(1);
    }

When we call C's initializer, it sets the $._initializing to true and then performs the function logic, and in this case, the function logic means initialization of B, which means we again called the initializer modifier, this time via B. But this time the _initializing bool is true, which hits the revert condition :

if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }

So the later snippet you shared is correct.

Also, contract B read initializing as true despite inheriting Initializer separately because the storage slot is constant, the same in all three contracts.

1
  • Thanks a lot for the detailed explanation, Zartaj! Commented Sep 24 at 18:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.