UUPS Proxies: Tutorial (Solidity + JavaScript)

Ah, I see, thanks for the clarification :+1:

How can I use

MarsV2 is Mars


MarsV3 is Mars

if Mars was already deployed in the blockchain?

Thank you for the great tutorial, the UUPSUpgradeable is only work with sol 0.8.2++? is there anyway to import it in the solc project that use 0.7? I cant find that smart contract in openzeppelin contract for v 0.7

Our contract will only work with the specified Solidity version. You can try copying the contract and adapting it for Solidity 0.7, but mind the risks of modifying the code.

await upgrades.upgradeProxy(myTokenV1.address, MyTokenV2)

Adding this to myToken test gives me an Ownable error. I pasted the final code into Remix and the owner function returns the 0x0 address.

Error: VM Exception while processing transaction: reverted with reason string 'Ownable: caller is not the owner'

After spending way too much time on this, I finally figured it out. The solidity code in the tutorial is missing the __Ownable_init(); function call in its initializer. Gonna leave my passing tests here for posterity.

// test/MyToken.test.ts

import { expect } from 'chai'
import { ethers, upgrades } from 'hardhat'
import { MyTokenV1 } from '../typechain/MyTokenV1'
import { MyTokenV2 } from '../typechain/MyTokenV2'

let myTokenV1: MyTokenV1
let myTokenV2: MyTokenV2

describe('MyToken', function () {
  it('deploys', async function () {
    const MyTokenV1 = await ethers.getContractFactory('MyTokenV1')
    myTokenV1 = (await upgrades.deployProxy(MyTokenV1, { kind: 'uups' })) as MyTokenV1
  it('upgrades', async function () {
    const MyTokenV2 = await ethers.getContractFactory('MyTokenV2')
    myTokenV2 = (await upgrades.upgradeProxy(myTokenV1, MyTokenV2)) as MyTokenV2
  it('adds with new function and variable', async function () {
    await myTokenV2.add(21)
    expect((await myTokenV2.num()).toString()).to.equal('21')
    await myTokenV2.add(21)
    expect((await myTokenV2.num()).toString()).to.equal('42')
// contracts/MyTokenV2.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol';
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyTokenV2 is Initializable, ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
    uint public num;

    function initialize() public initializer {
        __ERC20_init('MyToken', 'MTK');


        _mint(msg.sender, 1000 * 10**decimals());

    function _authorizeUpgrade(address) internal override onlyOwner {}

    function add(uint _num) public {
      num += _num;
1 Like

My bad, sorry about that! Thank you for debugging and finding out the cause. I've updated the code in the original post.

Thank you for updating the code. I noticed that the OpenZeppelin Contracts Wizard uses __UUPSUpgradeable_init(); in the initializer function. What does that function do? Should it be used? The tutorial also says that a constructor should not be used, but the code was updated to include one to address the UUPSUpgradeable vulnerability.

__UUPSUpgradeable_init is an empty function actually. The Wizard includes it because it follows our general recommendation which is to include all inits for all parents, so as not to require checking whether a function is empty or not, and to future-proof for the possibility that we add initialization logic at some point. So I've updated the code to reflect this recommendation.

I've also improved the part about the constructor to make things more clear.

Thanks for the feedback!

1 Like

A post was split to a new topic: How to use the variable __gep correctly in the proxy contract

When upgrading from version 2 to version 3, I notice in the video that you upgrade from mars to marsv3 by calling

const marsv3 = await hre.upgrades.upgradeProxy(mars, this.Marsv3);

I assume we update the deployed proxy and not the updated, so if we were on version 10 of the proxy, we would still upgrade this existing mars that was deployed.


Yes that's right. The proxy always stays at the same address and that's the contract that you deploy.

1 Like

Yup, managed to upgrade my contracts via the UUPS pattern. This makes sense as the proxy uses delegateCall on the implementation. Also verified the implementation contracts on Etherscan and have the Proxy point to the implementation. Great work guys!

1 Like

Regarding killing the upgradeability:
Can I kill it with only one upgrade by implementing an impossible check in the _authorizeUpgrade method?
For example if I upgrade to a new implementation which contains this function:

function _authorizeUpgrade(address) internal override {
    require(false, "no more upgrades allowed");

Or does the upgrade then fail, because of the security checks in the
function _upgradeToAndCallSecure
and I should just override the UUPS functions to directly call the upgradeToAndCall() if I want to allow the upgrade to a non upgradeable implementation?
Keep up the good work! Loving your videos as well.

1 Like

Indeed that upgrade will fail. Overriding to use the unsecured version would allow you to remove the entire upgradeability through two upgrades.

By the way, we're simplifying UUPS by removing the rollback test in the next version of OpenZeppelin Contracts, so removing upgradeability will be easier.

1 Like