Deploying Upgradeable Proxies and Proxy Admin from Factory contract

It's a good question, and I'd like to hear @frangio 's take as well.

I suppose if those BeaconProxies is already handling the three main concerns (namely, seperate storage, the proxying itself, and the ability to be upgraded), there isn't much of a need for a base contract that implements UUPS under the hood, or am I missing something?

Oh yeah absolutely. My bad I didn't notice the combination of the two approaches! You have to choose either beacons or UUPS.

2 Likes

Guess whose contracts just got that much smaller! Thanks a ton @TtheBC01

Just so I'm sure, I can now stop:

  • Inheriting UUPSUpgradeable
  • Overriding _authorizeUpgrade
  • Defining constructor initializer {}

But I should still:

  • Use implementations of contracts-upgradeable (not sure about this one...)
  • Use the initializer pattern and NOT define a non-empty constructor
    • And as such continue to use __SomePackage_init();

Would that be an accurate assessment of the changes needed?

2 Likes

Sounds good. You can continue defining constructor initializer {} as a general measure to prevent the implementation contract from being used.

2 Likes

Hi guys. I'm trying to create a factory contract that deploys upgradable contracts. I also want the factory contract to be upgradable. I'm able to upgrade the factory contract and I'm able to deploy the contract deployed from the factory but whenever I call a method in that contract, it gets reverted, exception is thrown. Any ideas?

pragma solidity 0.8.7;
import "./LeadersProxySwapContractUpgradable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract LeaderTrailerFactory is Initializable{
  address leaderProxyImplementation;

function initialize (
  ) public {
    leaderProxyImplementation = address(new LeadersProxySwapContractUpgradable());
    emit FactoryInit(leaderProxyImplementation);
  }
  event FactoryInit(address leaderProxyImplementation);

  function newLeaderWallet(
     address _pangolinRouterAddress,
     address _wavaxAddress,
     string memory _walletName,
     string memory _profilePictureUrl
   )
       payable
       public
   {
 
       ERC1967Proxy proxy = new ERC1967Proxy(
          leaderProxyImplementation,
          abi.encodeWithSelector(
            LeadersProxySwapContractUpgradable(payable(address(0))).initialize.selector,
              msg.sender,
         _pangolinRouterAddress,
         _wavaxAddress,
         payable(address(this)),
         liquidAddresses,
         _walletName,
         _profilePictureUrl
       ));
       address proxyAdd = address(proxy);
       wallets[msg.sender].push(proxyAdd);
       emit Created(proxyAdd, "Leader", msg.sender, block.timestamp);
   }
}

Here's the implementation contract:

pragma solidity 0.8.7;
import "./LeadersProxySwapContract.sol";
import "./LeaderTrailerFactory.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

contract LeadersProxySwapContractUpgradable is Initializable, UUPSUpgradeable, OwnableUpgradeable, LeadersProxySwapContract{

  function initialize(
    address _leader,
    address _pangolinRouterAddress,
    address _wavaxAddress,
    address payable _leaderTrailerFactory,
    address[] memory _liquidAddresses,
    string  memory _walletName,
    string memory _profilePictureUrl
    ) public {
        initializeLeaderWallet(
          _leader,
        _pangolinRouterAddress,
        _wavaxAddress,
        _leaderTrailerFactory,
        _liquidAddresses,
        _walletName,
        _profilePictureUrl);
        __Ownable_init();
       transferOwnership(0x66B9CC2257.....);
    }
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

}

And here's the contract that I'd like be able to upgrade that is deployed from the factory:

pragma solidity 0.8.7;
import '@pangolindex/exchange-contracts/contracts/pangolin-periphery/interfaces/IWAVAX.sol';
import '@pangolindex/exchange-contracts/contracts/pangolin-core/interfaces/IERC20.sol';
import "@pangolindex/exchange-contracts/contracts/pangolin-periphery/interfaces/IPangolinRouter.sol";
import "./LeaderTrailerFactory.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract LeadersProxySwapContract is Initializable{
..

  function initializeLeaderWallet (
    address _leader,
    address _pangolinRouterAddress,
    address _wavaxAddress,
    address payable _leaderTrailerFactory,
    address[] memory _liquidAddresses,
    string  memory _walletName,
    string memory _profilePictureUrl
    ) public {

      leader = _leader;
      pangolinRouterAddress = _pangolinRouterAddress;
      WAVAX = _wavaxAddress;
      leaderTrailerFactory = _leaderTrailerFactory;
      walletName = _walletName;
      profilePictureUrl = _profilePictureUrl;
      router = IPangolinRouter(pangolinRouterAddress);
      liquidAddresses=_liquidAddresses;
      tokenBalanceArray= new uint256[](liquidAddresses.length);
}

function method1....

}
1 Like

Okay I figured this out using BeaconProxy and UpgradableBeacon. For other people coming here now, the video I followed is here: https://www.youtube.com/watch?v=2acgw589jRM

Also, I had issues using ethers library although I can't be sure because it could have been other bugs. So I switched to web3 deployment and contract calls, I used hardhat as well. I did not use any of the plugins. If anyone sees any security issues or any thoughts for improvement would love any notes. To update, I created a new copy of LeaderWallet and named it LeaderWalletV2. Then I added a new function to the very end of the code in the contract to test the deployment. I tried to only include relevant parts of my code.

pragma solidity 0.8.7;

import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "./LeaderBeacon.sol";
import "./LeaderWallet.sol";


contract LeaderBeaconFactory {

  //address for the logic contract
  LeaderBeacon immutable thisLeaderBeacon;

  mapping(uint256 => address) walletsIndexedById;
  address prosperoRelayAddress;


  constructor(
    address implementation_,
    address _pangolinRouterAddress,
    address[] memory _liquidAddresses,
    address _prosperoRelayAddress
  ) {
    thisLeaderBeacon = new LeaderBeacon(implementation_, _prosperoRelayAddress, address(this));
    pangolinRouterAddress = _pangolinRouterAddress;
    liquidAddresses = _liquidAddresses;
    prosperoRelayAddress=_prosperoRelayAddress;
    
  }

  modifier onlyProspero {
    require(msg.sender == prosperoRelayAddress);
    _;
  }

  function newLeaderWallet(
    string memory _walletName,
    string memory _profilePictureUrl,
    address factoryAddress,
    uint _idForWallet
  )
  payable
  public
  {
    
      bytes memory dataOfLeaderWallet = abi.encodeWithSelector(
        LeaderWallet(payable(address(0))).initialize.selector,
        msg.sender,
        pangolinRouterAddress,
        factoryAddress,
        liquidAddresses,
        _walletName,
        _profilePictureUrl
      );
      BeaconProxy leaderWalletBeaconProxy = new BeaconProxy(
        address(thisLeaderBeacon),
        dataOfLeaderWallet
      );

    address proxyAddress = address(leaderWalletBeaconProxy);

    walletsIndexedById[_idForWallet]=proxyAddress;
    emit Created(proxyAddress, "Leader", msg.sender, block.timestamp, _idForWallet);

  }
  function getBeacon()
  external
  view
  onlyProspero
  returns
  (address){
    return address(thisLeaderBeacon);
  }
  function getWalletById(uint256 _id)
  public
  view
  returns(address)
  {
    return walletsIndexedById[_id];
  }
}


pragma solidity 0.8.7;

import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "./LeaderWallet.sol";

contract LeaderBeacon is Ownable{

  UpgradeableBeacon immutable beacon;
  address public leaderImplementation;
  address prosperoRelayAddress;
  address factoryAddress;

constructor(address _initLeaderImplementation, address _prosperoRelayAddress, address factAddress){
  beacon = new UpgradeableBeacon(_initLeaderImplementation);
  leaderImplementation=_initLeaderImplementation;
  prosperoRelayAddress=_prosperoRelayAddress;
  factoryAddress=factAddress;
  transferOwnership(prosperoRelayAddress);
}


function update(address _newLeaderImplementation) onlyOwner public {
  beacon.upgradeTo(_newLeaderImplementation);
  leaderImplementation=_newLeaderImplementation;
}

function implementation() public view returns (address){
  return beacon.implementation();//impAddress;
}

}

pragma solidity 0.8.7;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./LeaderBeaconFactory.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

import "@pangolindex/exchange-contracts/contracts/pangolin-periphery/interfaces/IPangolinRouter.sol";

contract LeaderWallet is Initializable{
 
  function initialize  (
    address _leader,
    address _pangolinRouterAddress,
    address _leaderBeaconFactory,
    address[] memory _liquidAddresses,
    string  memory _walletName,
    string memory _profilePictureUrl
  ) public initializer  {
....
}

function getInfo()
  public
  view
  returns (
    string memory,
    string memory,
    address,
    uint256

  )
  {
    return (walletName, profilePictureUrl, leader, lastTxId);
  }

  receive() external payable { }

}




1 Like

Hello Fransico

My humble understanding from your great UUPS tutorial is that the upgradeTo function will be called in Implementation not the proxy otherwise it will be a transparent proxy, did I get wrong?

If you don't mind going the extra mile and providing me with a simple snippet for an upgradeable factory pattern contract using UUPS, I will hugely appreciate it.

Thanks in advance Fransico

@sasha Look at this code that was shared earlier in the thread:

The upgradeTo function is always invoked/called in the proxy. The difference between UUPS and Transparent is where the function is defined.

1 Like

Thank you so much @frangio I looked at the example provided earlier in this threat and it is extremely helpful thanks to @julianmrodri .

Let me confirm my understanding that defining the updateTo function is done in the implementation through the _authorizeUpgrade function, right?

Many thanks in advance and Merry Christmas to you and all OZ team members

Not exactly. The upgradeTo function is defined by inheriting UUPSUpgradeable. This contract requires that you define _authorizeUpgrade.

1 Like

Do you guys know if it is possible that we pass {value: msg.value} somehow in here because my initializer is payable. Thanks
'''
ERC1967Proxy proxy = new ERC1967Proxy(
tokenImplementation,
abi.encodeWithSelector(MyTokenUpgradeable(address(0)).initialize.selector, name, symbol, initialSupply, owner)
);
'''

I believe your question has been answered here:

Yes, Eric kindly replied. thank you!

@frangio
I'm using hardhat to do some testing. I had built an upgradeable contract using uups. All works as expected by itself. I am now attempting to deploy that contract using BeaconProxy factory and an UpgradeableBeacon. I have removed the UUPS logic from the implementation contract. (removed UUPSUpgradeable library and removed the _authorizeUpgrade function. Similar to what @0xLostArchitect was saying on Oct. 22 post.)

So, long story short: The initial implementation deploys fine. The factory and beacon contracts deploy fine. I then call the factory function to create a new proxy, that seems to work too. However, when I try to call a function from that new proxy I receive a "Error: call revert exception". Here is my code (bare minimum on the implementation contract):

GolfEventBeacon.sol

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.10;

import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GolfEventBeacon is Ownable {
    UpgradeableBeacon immutable beacon;
    address public golfEventBlueprint;

    constructor(address _initBlueprint) {
        beacon = new UpgradeableBeacon(_initBlueprint);
        golfEventBlueprint = _initBlueprint;
        transferOwnership(tx.origin);
    }

    function update(address _newBlueprint) public onlyOwner {
        beacon.upgradeTo(_newBlueprint);
        golfEventBlueprint = _newBlueprint;
    }

    function implementation() public view returns (address) {
        return beacon.implementation();
    }

}

GolfEventFactory.sol

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.10;

import "hardhat/console.sol";
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "./GolfEventBeacon.sol";
import "./GolfEvent.sol";

contract GolfEventFactory {
    mapping(uint32 => address) private events;
    GolfEventBeacon immutable beacon;

    constructor(address _initBlueprint){
        beacon = new GolfEventBeacon(_initBlueprint);

    }

    function createGolfEvent(
        string calldata _eventSetName,
        string calldata _eventSetSymbol, 
        uint32 eventId
        ) external {

        BeaconProxy golfEvent = new BeaconProxy(address(beacon),
        abi.encodeWithSelector(GolfEvent(address(0)).initialize.selector, _eventSetName, _eventSetSymbol));

        events[eventId] = address(golfEvent);
    }

    function getEventAddress(uint32 eventId) external view returns (address) {
        return events[eventId];
    }

    function getBeaconAddress() public view returns (address) {
        return address(beacon);
    }

}

GolfEvent.sol


// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.10;

import "hardhat/console.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";

contract GolfEvent is Initializable, OwnableUpgradeable, ERC721URIStorageUpgradeable {

    function initialize(string memory _eventSetName, string memory _eventSetSymbol) payable external initializer{
        __Ownable_init();
        __ERC721_init(_eventSetName, _eventSetSymbol);
        console.log("%s name service deployed. Symbol: %s", _eventSetName, _eventSetSymbol);
    }

    //simple function to print out version
    function getVersion() pure public virtual returns (string memory) {
        return "GolfEvent_v1!";
    }

}

finally my run.js

const main = async () => {
    // The first return is the deployer, the second is a random account
    const [owner, superCoder] = await hre.ethers.getSigners();
  
    // deploy original implementation contract
    const v1ContractFactory = await hre.ethers.getContractFactory('GolfEvent');
    const v1Contract = await hre.upgrades.deployProxy(v1ContractFactory,
       ["pitty", "pty"]);
    await v1Contract.deployed();
    console.log("Initial Golf Event Contract deployed to: ", v1Contract.address);

    // Deploy the beaconproxy factory
    const factoryFactory = await hre.ethers.getContractFactory('GolfEventFactory');
    const factoryContract = await factoryFactory.deploy(v1Contract.address);
    await factoryContract.deployed();
    console.log("Factory Contract deployed to:", factoryContract.address);

    // Get becon contract address that was deployed automatically by the factory contract
    const beaconContract = await hre.ethers.getContractAt("GolfEventBeacon", await factoryContract.getBeaconAddress());
    console.log("beaconContract address: ", beaconContract.address);

    // Create first golf event proxy and get that address
    await factoryContract.createGolfEvent("FirstEvent","FEV",1);
    const firstEventContract = await hre.ethers.getContractAt("GolfEvent",await factoryContract.getEventAddress(1));
    console.log("First golf event address: ",firstEventContract.address);

    // call the getVersion function of the new proxy (THIS FAILS)
    const versionResult = await firstEventContract.getVersion();
    console.log("first golf event contract version: ", versionResult);
  }
    
  const runMain = async () => {
    try {
      await main();
      process.exit(0);
    } catch (error) {
      console.log(error);
      process.exit(1);
    }
  };
  
  runMain();

Using hardhat I run:

npx hardhat run scripts/run.js

and receive this exception:

Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] 
(method="getVersion()", data="0x", errorArgs=null, errorName=null, errorSignature=null,
 reason=null, code=CALL_EXCEPTION, version=abi/5.6.1)
{
  reason: null,
  code: 'CALL_EXCEPTION',
  method: 'getVersion()',
  data: '0x',
  errorArgs: null,
  errorName: null,
  errorSignature: null,
  address: '0x6D544390Eb535d61e196c87d6B9c80dCD8628Acd',
  args: [],
  transaction: {
    data: '0x0d8e6e2c',
    to: '0x6D544390Eb535d61e196c87d6B9c80dCD8628Acd',
    from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    gasLimit: BigNumber { value: "29021272" }
  }
}

Any thoughts on what I could be doing wrong? Thanks!

Hi @scoots,

For v1Contract, you want an implementation contract (because you are passing the implementation contract to your beacon proxy factory). upgrades.deployProxy returns a transparent or UUPS proxy, which is not what you need in this case. You can deploy the implementation directly instead.

So the first part should be like:

  // deploy original implementation contract
  const v1ContractFactory = await hre.ethers.getContractFactory('GolfEvent');
  const v1Contract = await v1ContractFactory.deploy();
  console.log("Initial Golf Event Contract deployed to: ", v1Contract.address);

Another way is to change your beacon proxy factory to take an actual beacon instead. That way you can use the upgrades plugins to deploy the beacon and implementation (which gives the additional benefit of validating that your implementation is upgrade safe). See the discussion in How to deploy new instances using beacon proxy from a factory when using @openzeppelin/hardhat-upgrades - #5 by gate3

@ericglau Awesome, that did the trick! Thanks for responding so quickly! I'll look into your recommendation of changing the beacon proxy factory next.

Hey guys, I was going to make an upgradable contract for a staking contract factory which proxy should I use for the development

Hi @0xAlfa, please see https://docs.openzeppelin.com/contracts/4.x/api/proxy for details on the different proxy patterns that are available.

Hi, I have two questions:

constructor(address upgrader) {
        UpgradeableBeacon _tokenBeacon = new UpgradeableBeacon(address(new MyTokenUpgradeable()));
        _tokenBeacon.transferOwnership(upgrader);
        tokenBeacon = address(tokenBeacon); //Shouldn't be tokenBeacon = address(_tokenBeacon)
    }

and if I want to run any methods of MyTokenUpgradeable, how should I call it? does this makes sense?

MyTokenUpgradeable(proxy).balanceOf(...)

Thank you!

You need to deploy a BeaconProxy pointing to the beacon instance. Then you can use it as MyTokenUpgradeable(proxy).balanceOf(...).