UUPS Proxies: Tutorial (Solidity + JavaScript)

@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

Just a question on the initialize functions. Wondering which version should be used? __ERC20_init or __ERC20_init_unchained? Besides, UUPSUpgradeable and OwnableUpgradeable are also inheriting from other upgradeable contracts. Should the initialize functions in those contracts be included? Thanks. @frangio

The recommendation is to use _init.

We have a custom workflow where we want to upgrade our contracts through governance proposals.
The steps we are using are:

  1. Transfer ownership of the contract to the governance contract.
  2. Make _authorizeUpgrade() only callable by the owner.
  3. Create a proposal to call proxy.upgradeTo() with the new address as the parameter.

The question is, how do we deploy a new instance of the logic contract without doing the upgrade at the same time? Using upgrades.deployProxy() deploys a new proxy contract, and upgraes.upgradeProxy() updates the existing contract but can only be called from an externally owned address, thus, can't be called from something like a governance contract.

You can use upgrades.prepareUpgrade! Check out the docs.

1 Like

Thanks for the tutorial!

Now that I understand that MyTokenV1 is an upgradeable smart contract deployed with an UUPS Proxy, is there an example on how I can deploy a Factory of MyTokenV1 smart contracts? I don't know if "Factory" is the right word to use in this case or if I should refer to "Clones"...

You can find examples of factory contracts in this thread:

Hi @frangio thank you for the presentation , it was really great & extremely helpful.
I just have 2 small questions:

  1. About 12 minutes into the video you mentioned that moving upgradeTo() to Implementation helps reduce overhead since calls to the Proxy can be delegated immediately without needing to check storage for msg.sender == admin,
    do you mean that in TransparentUpgradeableProxy pattern each time the Proxy contract receives any call its forced to check msg.sender == admin before deciding whether to delegate? or does this check only occur when certain types of function calls are made?

  2. In the demo example an upgradeable erc20 contract is used , wouldn’t an upgradeable erc20 be deemed "untrustworthy" by some since the token logic could be updated any time to something possibly malicious ?

Yes, this happens for every call.

Yes and no. The account that has upgrade permissions is not necessarily a single actor that can turn malicious, it may be a multisig or a DAO with good thresholds and parameters. There is always a risk though, even with those setups, and it should be managed appropriately. It is generally a good idea to restrict upgradeability so that potential risk is contained and minimized, and the ERC20 token in a system is often a good candidate to be immutable.

Note that there are very prominent tokens that are upgradeable, like USDC. I mention this to point out that a large portion of the space seems to accept it in some cases.

1 Like

Thank you @frangio ! Greatly appreciate all your work!

1 Like

Hi @frangio

Thank you for the great tutorial. Quick question from my side and sorry if this is a silly question but my understanding is that a UUPS proxy should have two components: A proxy contract and an implementation contract.

Is my understanding correct that the MyTokenV1 Contract in your original post is the implementation contract? If that is TRUE, please could you advise where we could see the actual code/logic for the Proxy contract?

@KamiWar You are correct, MyTokenV1 is the implementation contract. ERC1967Proxy is the UUPS proxy contract.

Thank you for the post @frangio

I understand the UUPS and how it is being implemented by OpenZeppelin:

  1. Implementation is Deployed
  2. Proxy is deployed by initializing the Implementation address in Proxy contract at a specific address location.

I am particularly concerned about the upgrade process, perhaps I read too many articles which muddled my understanding. It is said that it is the implementation contract's responsibility to upgrade the contract address of the new implementation contract. I have 2 questions with respect to this statement:
1. How can I view the Proxy contract? (May be seeing it would improve my thought process a lot)(Assuming I'm running the local blockchain with Hardhat)
2. And during the upgrade, does the old implementation contract change the implementation address at a specific address location in the Proxy contract?


What do you mean?


I mean to ask where can I find the proxy contract that is being created and deployed by OpenZeppelin when we run "upgrades.deployProxy()"

The function will return a contract with an address, that is the proxy.