UUPS Proxies: Tutorial (Solidity + JavaScript)

Is initialize() is only meant to be called once for the entire proxy's lifecycle? If new variables are introduced in a new upgraded implementation, what is the right pattern for initializing them?

1 Like

Call initialize on the new logic contract.
You can call initialize once by contract, so you can't call it anymore on the proxy contract, but you can call it once on new logic contract deployed

What do you mean? The initialize would be called through the proxy if an upgrade happens.

You can pass data on upgrade to call your logic contract with initialize info

@youtpout You need to deploy the implementation contract and then an instance of ERC1967Proxy

@leeren It's a good question. For testing you will probably want to have an initialize() function where you initialize all available variables. But for upgrading you will need a new method, something like initializeV2 where you only initialize the new things.

1 Like

Thanks, that clears it all up.

Hello I resolve my problem, it's just because I didn't reattach my proxy to my market contract in hardhat, so hardhat didn't recognize the method called.

Great tutorial! In your example, what is the significance of adding the initializer modifier to the constructor function?

@frangio Also another follow up question. If I understand this correctly, functions of the proxy itself (not the implementation contract) can only be called by ProxyAdmin. How can we get the ProxyAdmin object? I have only seen docs on using the Upgrade Plugin (i.e. the upgradeProxy function) to update a proxy's implementation.

Edit: nvm I think Error: No ProxyAdmin was found in the network manifest - #2 by ericglau answered my question. For those who might have the same question: for uups standard, there is no ProxyAdmin, and the deployer of the proxy can directly call those proxy admin functions (e.g. updating implementations)

1 Like

The initializer modifier in the constructor makes sure that the real initializer function can't be invoked on the implementation contract directly. In some cases, if the implementation can be initialized, that can be used as part of an exploit.

1 Like

Is there a part 2 to this article showing deployment, V2 and the upgrade?

There is no part 2 but feel free to create a new post and ask a question.

A post was split to a new topic: Use Chainlink AggregatorV3Interface with UUPSUpgradeable

Strangely I'm getting an error when attempting to deploy the proxy for a UUPS contract:

@openzeppelin/contracts/utils/Address.sol:191: Use of delegatecall is not allowed

I'm pretty sure the offending use is coming from UUPSUpgradeable itself... which the contract inherits from. I've made sure to specify kind: 'uups' in the deploy args as follows, hope this is correct (using typescript):

await upgrades.deployProxy(MyContractFactory, [args.name, args.symbol], {initializer: 'initialize', kind: 'uups'}) as MyContract

It works fine if I remove UUPSUpgradeable and deploy the transparent (default) type.

Any thoughts?

@JasperTimm Please use @openzeppelin/contracts-upgradeable instead of @openzeppelin/contracts to avoid this error.

That worked, thanks @frangio !

Maybe a better question would be - does it make sense to have a version of UUPSUpgradeable in the regular contracts directory?

Yes, the contract works but because of a limitation in our Upgrades Plugins it raises this validation error. See https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/240

1 Like

It caught my eye how you dealt with this one! Could you (or somebody) point out an explainer on using only type without variable name in arguments of a function declaration. It's rarely seen in Solidity examples, so any additional info would be appreciated!

It's known as an unnamed parameter.

In the example _authorizeUpgrade function doesn't do anything with the address parameter, but, because the function is overridden from a parent contract, you need to include the parameter to override the function. In solidity, it is perfectly fine to omit the parameter name if the parameter is not used, so, possibly to avoid compiler warnings, the name was excluded.

Implementation-wise it doesn't really matter. If you include the parameter name, you get a warning that you have an unused parameter but your contract will still compile.

1 Like