2

I have the following contract:

import { UUPSUpgradeable } from "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";

contract One is UUPSUpgradeable, OwnableUpgradeable {
    uint256 public var1;
    uint256 public var2;

  function initialize() public initializer {
    __Ownable_init();
    __UUPSUpgradeable_init();
  }

    function setVar1(uint256 _var1) public {
        var1 = _var1;
    }

    function setVar2(uint256 _var1) public {
        var2 = _var2;
    }

  function _authorizeUpgrade(address) internal virtual override onlyOwner {}

}

I deploy a proxy with hardhat-upgrades, then realize that setVar2() and var2 would be better placed in a separate file to make the contract more modular. So I create a Utils contract that I will inherit from.

My intuition is that I should be able to do this:

contract Utils{
    uint256 public var2;

    function setVar2(uint256 _var2) public {
        var2 = _var2;
    }
}

But then, wouldn't lose the upgradeability of everything in Utils? If I want to preserve the upgradeability of what I have going on in contract One would I need to do deploy another* proxy? (for Utils) resulting in the following code?

import { UUPSUpgradeable } from "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";

contract Utils is UUPSUpgradeable, OwnableUpgradeable {
    uint256 public var2;

  function initialize() public initializer {
    __Ownable_init();
    __UUPSUpgradeable_init();
  }

    function setVar2(uint256 _var2) public {
        var2 = _var2;
    }

  function _authorizeUpgrade(address) internal virtual override onlyOwner {}

}

then update One to have:

contract One is Utils {
    uint256 public var1;

    function setVar1(uint256 _var1) public {
        var1 = _var1;
    }

  function _authorizeUpgrade(address) internal virtual override onlyOwner {}

}

Questions:

  • Do I need to add an initializer and inherit UUPSUpgradeable and OwnableUpgradeable to Utils to maintain upgradeability?
  • In the latest version of contract One ... do I need _authorizeUpgrade? Since it's already inherited from Utils?
  • Is it ok that in the latest version of contract One I got rid of the initializer?

1 Answer 1

1

Great question, very well laid out.

  1. Inheriting UUPSUpgradeable and OwnableUpgradeable from Utils like you have done is the way to go. When building an upgradeable contract you should build in upgradeability from the base contract that is being inherited from, which in this case is Utils. You're not deploying another proxy, you're using the same proxy for One and Utils with this pattern.

  2. Nope, you don't need _authorizeUpgrade in One. Since you're inheriting from Utils and the method is public you will be able to call it from the address to which One is deployed.

  3. Yep, you technically don't need it in One as you're not doing anything in the context of that contract, however I would personally include it.

I would add this to One:

function initialize() public initializer {
    Utils.initialize();
}

and then modify utils:

function initialize() public onlyInitializing {
   [same as before]
}

As a side note I would also reserve a set of storage slots in Utils so that you have the freedom to add more variables during an upgrade. With your current code, if you wanted to reserve another storage slot in Utils at an upgrade, you would actually write over var1 in One. To reserve storage slots, add the following under var2 in Utils (to reserve 49 slots as an example):

uint256[49] __gap;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.