How to implement OZ UUPS to make a non token contract upgradable?

I have a non-token contract that I want to make upgradable using the OZ uups contract. I have used it to make a token contract upgradable, but what is the most appropriate way to implement it in any other general contract? For instance, how does one implement it in the default hardhat greeter contract to make it upgradable?

// IMPLEMENT UUPS UPGRADABILITY IN THIS CONTRACT
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Greeter {
    string private greeting;

    constructor(string memory _greeting) {
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }
}

:computer: Environment

Hardhat

1 Like

Hi @hasanza,

To make a non-token contract upgradable, you could use the Contracts Wizard to create an ERC20 upgradable contract, remove the ERC20 parts, and add in the relevant parts for your implementation.

For the example that you provided:

  1. Go to https://wizard.openzeppelin.com/
  2. Change name to Greeter.
  3. Choose ERC20, enable Upgradability, and choose UUPS.
  4. Copy and paste the contract into a new Greeter.sol.
  5. Remove the ERC20 related inheritance and initializer calls.
  6. Add the Greeter initialization implementation into the initialize function.
  7. Add the rest of the Greeter implementation.

The completed example would look like this:

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

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

import "hardhat/console.sol";

contract Greeter is Initializable, OwnableUpgradeable, UUPSUpgradeable {
    string private greeting;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize(string memory _greeting) initializer public {
        __Ownable_init();
        __UUPSUpgradeable_init();
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        onlyOwner
        override
    {}

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }
}

This is what I ended up doing and it worked; but what is the purpose of

    constructor() initializer {} 

If we are already using

initialize() initializer public {...} 

? I noticed that its new in the wizard as well.

initialize() initializer public {...} initializes the proxy, whereas constructor() initializer {} initializes the implementation contract itself by automatically marking it as initialized when it is deployed. See https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable

Is it important to use constructor() initializer {} if we are using the initialize() function for all constructor logic? Asking because it was not there in the Wizard some time ago and was added recently.

Yes, it is important because the initialize() function only initializes the proxy contract. This was added due to UUPSUpgradeable Vulnerability Post-mortem

Ah, I see; I did not know this! Thanks for the info :+1:

1 Like