Implementing Cheap NFT Clones Using BeaconProxy and UpgradableProxy Patterns

2022-02-14

Implementing Cheap NFT Clones Using BeaconProxy and UpgradableProxy Patterns

Introduction

Non-fungible tokens (NFTs) have taken the blockchain space by storm, offering a unique way to represent ownership of digital assets. However, deploying individual NFT contracts can be expensive in terms of gas fees. In this article, we'll explore how to implement cheap NFT clones using the BeaconProxy and UpgradableProxy patterns. We'll delve into the architecture of an NFTFactory smart contract, which leverages these patterns to produce cost-effective, upgradable NFTs.

Why Use BeaconProxy and UpgradableProxy Patterns?

The BeaconProxy and UpgradableProxy patterns are essential tools for optimizing the deployment and management of NFTs on the blockchain. Let's break down what each of these components offers:

BeaconProxy

The BeaconProxy pattern is a "beacon" pointing to a logic contract. When you deploy a new proxy contract, it will automatically use the logic contract through the proxy, thus eliminating the need to deploy the same logic multiple times. This is particularly useful for saving on gas costs when you need to deploy multiple instances of the same contract.

Advantages:

  • Cost-Efficiency: Saves gas by reusing the logic contract for multiple deployments.
  • Ease of Management: Upgrading the logic in one place (the upgradable proxy) automatically upgrades all associated proxy contracts.

UpgradableProxy

The UpgradableProxy pattern allows for the logic of a deployed contract to be changed, offering the flexibility to fix bugs or add new features. Unlike traditional smart contracts, which are immutable, an upgradable proxy can point to new logic without changing the contract's address, ensuring continuity and trust.

For each call, the beacon proxy gets the implementation address from the UpgradableProxy

Advantages:

  • Flexibility: Allows for the introduction of new features or fixes.
  • Continuity: The contract address remains the same even after an upgrade, which is crucial for maintaining references and integrations in other parts of a system.

By combining these two patterns, you can deploy NFTs that are both cost-efficient and flexible, making it easier to manage and upgrade them in the long run.

The Architecture

NFTFactory Smart Contract

The NFTFactory smart contract serves as the backbone of our architecture. It uses the BeaconProxy and UpgradableProxy patterns to manage the creation and upgrading of NFTs.

pragma solidity ^0.8.0;

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

contract NFTFactory {
    UpgradeableBeacon immutable beacon;

    event TokenCreated (
      address indexed cloneAddress
    );

    constructor(address implementationAddress) {
        beacon = new UpgradeableBeacon(implementationAddress);
        transferOwnership(msg.sender);
    }

    function upgradeImplementation(address newImplementation) public onlyOwner {
        beacon.upgradeTo(newImplementation);
    }

    function createNFT() public returns (address) {
        BeaconProxy token = new BeaconProxy(
            address(beacon),
    abi.encodeWithSelector(TokenV1(address(0)).initialize.selector)
        );
        
        emit TokenCreated(address(token));
        address(token);
    }
}

Implementing BeaconProxy

The BeaconProxy pattern allows us to specify a beacon containing the logic contract's address. All BeaconProxies will point to this logic contract (by getting the address from the UpgradableBeacon), making it easier to manage upgrades.

// Inside the NFTFactory contract
function setLogic(address _newLogic) public {
    UpgradeableBeacon(beacon).upgradeTo(_newLogic);
}

Implementing UpgradableProxy

The UpgradableProxy pattern provides the ability to upgrade the logic contract, which is essential for long-term projects that may require updates or fixes.

After upgrading the logic contract address, any subsequent calls to the beacon proxy will use this new address and thus, all the new logic.

// Inside the NFTFactory contract
function upgradeNFT(address _nftAddress, address _newLogic) public {
    BeaconProxy(_nftAddress).upgradeTo(_newLogic);
}

Combining Both Patterns

By combining both BeaconProxy and UpgradableProxy patterns, we can create a robust system for managing NFTs that is both cost-effective and flexible.

Please note that you only need the TokenFactory for this approach to work. OpenZepeling provides both BeaconProxy and UpgradableBeacon.

// Inside the NFTFactory contract
function createAndUpgradeNFT(address _newLogic) public returns (address) {
    address nft = createNFT();
    upgradeNFT(nft, _newLogic);
    return nft;
}

Diagrams

Here is a set of diagrams that illustrate the process of setup, normal usage, and upgrade.

1. Structure setup

Deploy the logic contract, the token factory, and the UpgradableBeacon.

Content description

2. Creating a clone

Using the TokenFactory, we create a lightweight BeaconProxy (without the whole logic code)

Content description

3. Interacting with the NFT

Users call the BeaconProxy which delegates all the calls to the Logic V1.

Content description

4. Upgrading the logic contract

By calling the UpgradableBeacon method "upgradeTo(new address)" this SmartContract changes the logic to what it points to. From this point, all the following calls to a BeaconProxy will fetch the new address and will delegate calls to it.

Content description

5. Using the new logic contract

Content description

Conclusion

Implementing cheap NFT clones doesn't have to be a daunting task. By leveraging the BeaconProxy and UpgradableProxy patterns, you can create an efficient system for managing NFTs. The NFTFactory smart contract serves as a powerful tool for deploying and upgrading NFTs, offering a balance between cost-effectiveness and flexibility.