Why are _initializing and isTopLevelCall variables used in Initializable contract of Openzeppelin?

535 views Asked by At

This is abstract contract in the context of Proxy pattern:

abstract contract Initializable {
    bool private _initialized;
    bool private _initializing;

    modifier initializer() {
        require(_initializing || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }
}

contract UpgradebleTest1 is Initializable {
    uint public x;

    function initialize(uint _x) public initializer {
        x = _x;
    }
}

I don't understand the necessity of _initializing and isTopLevelCall. Is not enough doing control using only _initialized?

Thanks,

2

There are 2 answers

1
Petr Hejda On BEST ANSWER

The _initializing and isTopLevelCall combination allow for chained calls with the initializer modifier:

contract UpgradebleTest1 is Initializable {
    uint public x;

    function initialize(uint _x) public initializer {
        internalInit(_x);
    }

    function internalInit(uint _x) internal initializer {
        x = _x;
    }
}

Without the _initializing and isTopLevelCall check, the initializer modifier would pass on the first call (initialize()) but fail on the second call (internalInit()).

modifier initializer() {
    require(!_initialized, "Initializable: contract is already initialized");
    _initialized = true;
}
1
Cengo On
abstract contract Initializable {
        bool private _initialized;
    
        modifier initializer() {
            require(!_initialized, "Initializable: co...");
            _;
            _initialized = true;
        }
    }
    
contract UpgradebleTestParent is Initializable {
    uint public x;

    function initialize(uint _x) internal initializer {
        x = _x;
    }
}

contract UpgradebleTestMain is UpgradebleTestParent {
    function init(uint _x) public initializer {
        initialize(_x);
    }
}

If it is as above, it would execute same logic, but _initialized = true; would be executed twice unnecessarily, right? However, it seems like cheaper than the previous one which has a one more variable and related additional instructions? Isn't it?