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?
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
@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.
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)
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.
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.
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
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.