How to use BeaconProxy with UpgradeableBeacon

:computer: Environment
Hardhat
:memo:Details
I’m having trouble understanding how to use the UpgradeableBeacon and BeaconProxy contracts, as well as the Hardhat Upgrades Plugin to achieve my desired use case

  • I have an implementation contract that defines functions for a bonding curve token
  • I want to be able to deploy proxy contracts that each represent their own token (so each has its own name, symbol, etc.) but delegate all function calls to the implementation contract

The reason I’m attracted to the Beacon proxy model is that I would be able to upgrade the implementation contract over time and all proxies would be upgraded with a single transaction.

However, I’m confused as to how I would implement this. So far, I have this structure for my implementation contract. *note that the tokens are not ERC20 tokens, so that’s why the ERC20 standard isn’t being inherited here.

Implementation contract

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

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

contract MarketFactory {
    // ======== Structs ========
    struct Parameters {
        string name;
        string symbol;
    }

    // ======== Immutable storage ========
    // address for the logic contract
    address immutable logic;

    // ======== Mutable storage ========
    Parameters public parameters;

    // ======== Constructor ========
    // the constructor deploys an initial version that will act as a template
    constructor(address _logic) {
        logic = _logic;
    }

    // ======== Deploy contract ========
    function createMarket(string calldata _name, string calldata _symbol)
        external
        returns (address)
    {
        parameters = Parameters({name: _name, symbol: _symbol});
        BeaconProxy proxy = new BeaconProxy(
            UpgradeableBeacon(logic),
            parameters
        );
        delete parameters;
        return address(proxy);
    }
}

I’m confused as to whether this is the correct way to implement a factory that deploys beacon proxies. Could someone give me some guidance on whether I am on the right track? Because with this method, I am unclear as to how I would change the implementation contract (do I have to create a separate setter function that allows the owner to change the logic address?)

Any help would be appreciated.

You need to create one instance of the UpgradeableBeacon contract.
Then when you want to upgrade it’s logic, you call the upgradeTo function on it.

Something like this:

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

contract MarketFactory {
    // ======== Structs ========
    struct Parameters {
        string name;
        string symbol;
    }

    // ======== Immutable storage ========
    // upgradeable beacon
    UpgradeableBeacon immutable upgradeableBeacon;

    // ======== Mutable storage ========
    Parameters public parameters;

    // ======== Constructor ========
    // the constructor deploys an initial version that will act as a template
    constructor(address _logic) {
        upgradeableBeacon =  UpgradeableBeacon(_logic);
    }

    function upgrade(address newLogicImpl) onlyOwner public {
        upgradeableBeacon.upgradeTo(newLogicImpl);
    }

    // ======== Deploy contract ========
    function createMarket(string calldata _name, string calldata _symbol)
        external
        returns (address)
    {
        parameters = Parameters({name: _name, symbol: _symbol});
        BeaconProxy proxy = new BeaconProxy(
           upgradeableBeacon,
            parameters
        );
        delete parameters;
        return address(proxy);
    }
}
3 Likes

As for the Upgrades Plugin for Hardhat, it doesn’t yet support beacon proxies. You can suscribe to this issue to get notified when it’s built.

You can look into the prepareUpgrade function to deploy your implementation contracts for use with beacons.

The constructor of BeaconProxy takes two arguments address beacon and bytes memory data. I think in your createMarket function, parameters should be encoded with an funtion such as initialize to create the calldata abi.encodeWithSignature("initialize(string memory, string memory)", parameters.name, parameters.symbol);.