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.
2. Creating a clone
Using the TokenFactory, we create a lightweight BeaconProxy (without the whole logic code)
3. Interacting with the NFT
Users call the BeaconProxy which delegates all the calls to the Logic V1.
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.
5. Using the new logic contract
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.