When should `initialize` function be called? What should be put in it?

Hey guys, I'm doing a simple TransparentUpgradeableProxy + ERC20Upgradeable pattern and I'm just wondering what should be put in the initialize function. I have an impression that function calls that are put in the constructor can be moved to the initialize() function. Is it OK to put _mint(owner(), amount); in this function? I think most importantly, is this function called through the proxy or on this contract directly? If the former is true, it must be called after the proxy is deployed. If the latter is true, then how can the state changes be reflected in the proxy contract? Thanks.

contract UpgradeableTest is ContextUpgradeable, OwnableUpgradeable, UUPSUpgradeable, ERC20Upgradeable {
    function initialize() external initializer {
        __Ownable_init_unchained();
        __UUPSUpgradeable_init();
        __ERC20_init("Test Token", "TT");
        _mint(owner(), 1_000_000_000*10**18);
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

Hi @maxareo,
you can place the mint function in your initialize function, it is ok.
You don't call this contract directly. Once it is initialized you call the proxy contract and it delegates every call (that is NOT coming from the owner of the proxy) to the implementation contract.
So you will need to use the address of the proxy when you will call it.

1 Like

Thanks @Allennick for the quick answer. Then another question would naturally arise which is suppose another ERC20Upgradeable is deployed to replace the existing one, and the function upgradeToAndCall in the proxy contract is called to set up the new implementation contract. In this process, I encountered the already initialized error, which means, the state variables in the Initializable contracts are also written in the proxy contract. This makes the call on upgradeToAndCall fail. Wondering if I am missing anything here. Thanks.

When you want to deploy another implementation, let's call it IMPL2.
Once you deploy IMPL2 with the same proxy you will not be able to call the initializer function anymore because you called it when you deployed your first implementation. This is the core idea of the upgradeable contract, you can't call the initialize function more than once.
When you call the function upgradeTo(address) you just need to pass the new address of your implementation, the new initializer will never be called

1 Like

@Allennick Thanks, you nailed it. Wondering what upgradeToAndCall would do and when it would be used? Apparently, it's not for calling the initialize function.

I’ve never used it so I can’t really tell you what’s the best fit for that function. I would use it when I have to upgrade a smart contract and immediately call a function on the new implementation

1 Like

@frangio Please take a look at this:

I found a paragraph in the section On Upgradeability on the page here:

It looks like the requirement is the initialize function can only be called once on the logic contract, but there is no argument on the proxy contract. Wondering if it is also true that initialize function in called only once on the proxy side.

Well you technically can call the initialise function from the proxy multiple times but that would cause a revert caused by the revert in the initialiser modifier.

1 Like

I am in similar situation, trying to add mint in initialize(), yet in my situation the minting is done by some other contract(i.e. Processor) which in turn needs to call back the token contract, equivalent to doing sth like this.owner() in initializer, and it turns out it can't be done unlike the OP example which used owner() directly.
In short it goes like this:
Token.initialize->Processor.mint->Token.owner->Revert!
So is there any other way to access contract methods in initializer?