Why UUPS implementation cannot be initialized by calling the function directly

I'm trying to understand why no one can call initialize function in a UUPS implementation contract directly. So this fucntion has an initializer modifier that look like this

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);
        }
    }

When I try calling the function directly I get the InvalidInitialization() error. Ok... let's look at the code and try to understand why...

InitializableStorage storage $ = _getInitializableStorage(); returns a struct with uint64 and a boolean which should be 0 and false by default right?

so the next 2 lines

bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;

should be true and 0
but they are not judging by the revert error.... Could you explain please, it seems like I'm missing the point that I don't know about or sth. Thanks

If the implementation contract has

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

then $._initialized would be non-zero, since that is intended to prevent initializing the implementation directly.

Otherwise, can you share what your implementation contract looks like, and how you are calling the initializer?

yes I have it like you've showed _disableInitializers() is inside of the implementation constructor.

Ok. But what happens with those vars in the modifier, when the initialize() is being called through the proxy with a delegate call? How is it different? Thank you

the difference is that this time when delegateCall that INITIALIZABLE_STORAGE struct slot is indeed true and 0 becuase it's from the proxy storage, and so the compiler skips those if statement... I guess

Right, when called through delegateCall, it reads from proxy storage so the storage variables are still at theirs defaults when you first call the initializer.

cool, thanks a lot! :slight_smile: