UUPS Deploy Implementation Contract First

Hi,

I am trying to write a contract upgradeable by admin but not without DAO permission.
My goal is;

  • Deploy implementation contract
  • Create proposal for upgrade with deployed implementation contract address
  • Get permission from DAO and upgrade

I am stuck with it. Am I using wrong pattern? Or is there a another way to do it?

Best regards

Solidity Code;

contract Mars is Initializable,UUPSUpgradeable,OwnableUpgradeable {
    mapping(address=>bool) private _isUpgradeAllowed;
    function initialize(address firstImp)public initializer{
        __Ownable_init();
        _isUpgradeAllowed[firstImp]=true;
        _mint(msg.sender, 600 * 10 ** decimals());
    }
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner{
        require(_isUpgradeAllowed[newImplementation],"Upgrade not allowed");
        _isUpgradeAllowed[newImplementation]=false;
    }
    function allowUpgradeFor(address newImp)external {
        _isUpgradeAllowed[newImp]=true;
    }
}
//This is 1st implementation
contract MarsV2 is Mars {
    mapping(address=>bool) private _isUpgradeAllowed;
    uint number;
    function version()pure external returns(string memory){
        return "v2";
    }
}

Deployment code;

//Deploy first implementation and deploy proxy
const mars = await hre.upgrades.deployProxy(this.Mars,["1st Implementation address"],{kind:'uups'});
//New upgrades 
const marsv2 = await hre.upgrades.upgradeProxy(mars,this.MarsV2);

Hello @mr3

You say you are stuck, but you don't say what is blocking you, so it's difficult to help.

What I can see in your code:

  • I don't understand the need for _isUpgradeAllowed[firstImp]=true;. Your proxy will already be set, so you don't need to authorize the upgrade
  • the allowUpgradeFor is not protected. It should be restricted to the DAO
  • you are using a mapping (address → bool) but you could just be using an address pendingImplementation. Having a mapping means multiple impls could be allowed, and the owner could then upgrade to them in any order, at any time, which might be breaking.

Finally, your deployment code doesn't include a call to allowUpgradeFor so its normal that the upgrade doesn't work. You should probable do

  1. deploy proxy
  2. prepare upgrade → gives you the address of the new implementation
  3. allowUpgradeFor
  4. execute upgrade.
1 Like

Hello @Amxx,
Thank your for help, I solved it with your recommendation. I missed function in doc (https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#prepare-upgrade). I aggree with you my question was not clear. Whole question was about prepare(deploy) implementation contract before upgrading which is solved by "prepareUpgrade".

Thank your for help
Best regards

Smart Contracts;

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

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

contract Mars is Initializable,ERC20Upgradeable,UUPSUpgradeable,OwnableUpgradeable {
    address private _pendingImplementation;
    function initialize()public initializer{
        __ERC20_init("Mars", "MARS");
        __Ownable_init();
        _mint(msg.sender, 600 * 10 ** decimals());
    }
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner{
        require(newImplementation == _pendingImplementation,"Pending address is different");
    }
    //WILL EXECUTED BY DAO
    function setNewImplementation(address newImp)external {
        _pendingImplementation=newImp;
    }
}
contract MarsV200 is Mars {
    address private _pendingImplementation;
    uint number;
    function version()pure external returns(string memory){
        return "v2.0.0";
    }
}
contract MarsV201 is Mars {
    address private _pendingImplementation;
    uint number;
    string str;
    function version()pure external returns(string memory){
        return "v2.0.1";
    }
}

Test Code;

require('hardhat');
let proxy_address;

before("get factories",async function(){
    this.Mars = await hre.ethers.getContractFactory("Mars");
    this.MarsV200 = await hre.ethers.getContractFactory("MarsV200");
    this.MarsV201 = await hre.ethers.getContractFactory("MarsV201");
})
it("Deploy proxy and make first upgrade with 1st implementation",async function(){
    //Deploy Proxy 
    const mars = await hre.upgrades.deployProxy(this.Mars,{kind:'uups'})
    console.log("Mars Proxy address is : "+mars.address);
    let tx= await mars.name();
    console.log(tx.toString());
    proxy_address=mars.address;
    //Prepare upgrade for 1st implementation
    const marsImp1 = await hre.upgrades.prepareUpgrade(mars.address,this.MarsV200);
    console.log("1st implementation address is : "+marsImp1);
    //Allow implementation(Will executed by Dao)
    await mars.setNewImplementation(marsImp1);
    //Make upgrade(1st implementation)
    const marsv200 = await hre.upgrades.upgradeProxy(mars,this.MarsV200);
    console.log("After upgrade : "+marsv200.address);
    //Test upgrade
    const upgradedMars = await this.MarsV200.attach(mars.address);
    let txV21=await upgradedMars.name();
    console.log(txV21.toString());
    let txV22=await upgradedMars.version();
    console.log(txV22.toString());
})
it("First realy upgrade",async function(){
    //Get proxy
    const mars = await this.Mars.attach(proxy_address);
    console.log(mars.address);
    //Prepare Upgrade
    const marsUpgrade1 = await hre.upgrades.prepareUpgrade(mars.address,this.MarsV201);
    console.log("1st upgrade implementation address is : "+marsUpgrade1);
    //Allow implementation(Will executed by Dao)
    await mars.setNewImplementation(marsUpgrade1);
    //Make upgrade
    const marsv201 = await hre.upgrades.upgradeProxy(mars,this.MarsV201);
    console.log("After upgrade : "+marsv201.address);
    //Test upgrade
    const upgradedMars = await this.MarsV201.attach(mars.address);
    let txV21=await upgradedMars.name();
    console.log(txV21.toString());
    let txV22=await upgradedMars.version();
    console.log(txV22.toString());
})