Skip to content

Commit 8aeb509

Browse files
Michiel Muldersgitbook-bot
Michiel Mulders
authored andcommitted
Upgrade ERC-721 token with UUPS pattern
1 parent a2f218d commit 8aeb509

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* [Smart Contracts](tutorials/smart-contracts/README.md)
1414
* [How to Mint & Burn an ERC-721 Token Using Hardhat and Ethers (Part 1)](tutorials/smart-contracts/how-to-mint-and-burn-an-erc-721-token-using-hardhat-and-ethers-part-1.md)
1515
* [How to Set Access Control, a Token URI, Pause, and Transfer an ERC-721 Token Using Hardhat (Part 2)](tutorials/smart-contracts/how-to-set-access-control-a-token-uri-pause-and-transfer-an-erc-721-token-using-hardhat-part-2.md)
16+
* [How to Upgrade an ERC-721 Token with OpenZeppelin UUPS Proxies and Hardhat (Part 3)](tutorials/smart-contracts/how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md)
1617
* [How to Verify a Smart Contract on HashScan](tutorials/smart-contracts/how-to-verify-a-smart-contract-on-hashscan.md)
1718
* [Deploy a Smart Contract Using Remix](tutorials/smart-contracts/deploy-a-smart-contract-using-remix.md)
1819
* [Deploy a Smart Contract Using Hardhat and Hiero JSON-RPC Relay](tutorials/smart-contracts/deploy-a-smart-contract-using-hardhat-hedera-json-rpc-relay.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# How to Upgrade an ERC-721 Token with OpenZeppelin UUPS Proxies and Hardhat (Part 3)
2+
3+
In this tutorial, you'll learn how to upgrade your ERC-721 smart contract using the [OpenZeppelin UUPS](https://docs.openzeppelin.com/upgrades-plugins/proxies) (Universal Upgradeable Proxy Standard) pattern and Hardhat. We'll first cover how the upgradeable proxy pattern works, then go through step-by-step implementation and upgrade verification, explaining each part clearly.
4+
5+
{% hint style="info" %}
6+
You can take a look at the **complete code** in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn).
7+
{% endhint %}
8+
9+
## Understanding the Upgradeable Proxy Pattern (Simplified)
10+
11+
In traditional smart contracts, you can't change the logic once deployed, which can be risky if you find bugs or want to add new features. The upgradeable proxy pattern solves this by separating your contract into two parts:
12+
13+
1. **Proxy Contract**: Stores the state (data) and delegates all calls to the logic contract.
14+
2. **Logic Contract**: Contains the actual business logic and can be replaced or upgraded.
15+
16+
When you upgrade your smart contract, you deploy a new logic contract and point your proxy contract to this new logic. The proxy stays at the same address, retaining your data and allowing seamless upgrades.
17+
18+
**Important Note:** In upgradeable contracts, constructors aren't used because the proxy doesn't call the constructor of the logic contract. Instead, we use an `initialize` function marked with the `initializer` modifier. This function serves the role of the constructor—setting up initial values and configuring inherited modules like `ERC721` or `Ownable`. The `initializer` modifier ensures this function can only be called once, helping protect against accidental or malicious re-initialization.
19+
20+
***
21+
22+
## Prerequisites
23+
24+
* ⚠️ **Complete** [**tutorial part 1**](how-to-mint-and-burn-an-erc-721-token-using-hardhat-and-ethers-part-1.md) **as we continue from this example. Part 2 is optional.**
25+
* Basic understanding of smart contracts.
26+
* Basic understanding of [Node.js](https://nodejs.org/en/download) and JavaScript.
27+
* Basic understanding of [Hardhat EVM Development Tool](https://hardhat.org/hardhat-runner/docs/guides/project-setup) and [Ethers](https://docs.ethers.org/v5/).
28+
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
29+
30+
***
31+
32+
## Table of Contents
33+
34+
1. [Step 1: Set Up Your Project](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#step-1-set-up-your-project)
35+
2. [Step 2: Create Your Initial Upgradeable ERC-721 Contract](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#step-2-create-your-initial-upgradeable-erc-721-contract)
36+
3. [Step 3: Deploy Your Upgradeable Contract](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#step-3-deploy-your-upgradeable-contract)
37+
4. [Step 4: Upgrade Your ERC-721 Contract](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#step-4-upgrade-your-erc-721-contract)
38+
5. [Step 5: Deploy the Upgrade and Verify](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#step-5-deploy-the-upgrade-and-verify)
39+
6. [Why Use the UUPS Pattern?](how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3.md#why-use-the-uups-pattern)
40+
41+
***
42+
43+
## Step 1: Set Up Your Project
44+
45+
Install necessary dependencies if you haven't done so. For part 3 of this tutorial series, we're adding two extra dependencies:
46+
47+
* `@openzeppelin/contracts-upgradeable` : This is a version of the OpenZeppelin Contracts library designed for upgradeable contracts. It contains modular and reusable smart contract components that are compatible with proxy deployment patterns, such as UUPS.
48+
* `@openzeppelin/hardhat-upgrades` : This Hardhat plugin simplifies deploying and managing upgradeable contracts. It provides utilities like `deployProxy` and `upgradeProxy` and automatically manages the underlying proxy contracts. This plugin is imported in the `hardhat.config.js` file, so we can use it.
49+
50+
<pre class="language-javascript"><code class="lang-javascript"><strong>// hardhat.config.js
51+
</strong><strong>require("dotenv").config();
52+
</strong>require("@nomicfoundation/hardhat-toolbox");
53+
require("@openzeppelin/hardhat-upgrades"); // Plugin for upgradeable contracts
54+
require("@nomicfoundation/hardhat-ethers");
55+
</code></pre>
56+
57+
## Step 2: Create Your Initial Upgradeable ERC-721 Contract
58+
59+
Create `erc-721-upgrade.sol` in the `contracts` directory:
60+
61+
```solidity
62+
// SPDX-License-Identifier: MIT
63+
// Compatible with OpenZeppelin Contracts ^5.0.0
64+
pragma solidity ^0.8.22;
65+
66+
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
67+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
68+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
69+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
70+
71+
contract MyTokenUpgradeable is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
72+
uint256 private _nextTokenId;
73+
74+
/// @custom:oz-upgrades-unsafe-allow constructor
75+
constructor() {
76+
_disableInitializers();
77+
}
78+
79+
function initialize(address initialOwner) public initializer {
80+
__ERC721_init("MyTokenUpgradeable", "MTU");
81+
__Ownable_init(initialOwner);
82+
__UUPSUpgradeable_init();
83+
}
84+
85+
function safeMint(address to) public onlyOwner returns (uint256) {
86+
uint256 tokenId = _nextTokenId++;
87+
_safeMint(to, tokenId);
88+
return tokenId;
89+
}
90+
91+
function _authorizeUpgrade(address newImplementation)
92+
internal
93+
override
94+
onlyOwner
95+
{}
96+
}
97+
98+
```
99+
100+
* **`initialize` function**: Replaces the constructor in upgradeable contracts, setting initial values and calling necessary initializers.
101+
* **`initializer` modifier**: Ensures the initialize function is only called once.
102+
* **`_authorizeUpgrade`**: Ensures only the owner can authorize upgrades.
103+
104+
Compile the contract:
105+
106+
```bash
107+
npx hardhat compile
108+
```
109+
110+
## Step 3: Deploy Your Upgradeable Contract
111+
112+
Create `deploy-upgradeable.js` under the `scripts` directory:
113+
114+
```javascript
115+
const { ethers, upgrades } = require("hardhat");
116+
117+
async function main() {
118+
const [deployer] = await ethers.getSigners();
119+
120+
const Token = await ethers.getContractFactory("MyTokenUpgradeable");
121+
const token = await upgrades.deployProxy(Token, [deployer.address], { initializer: "initialize" });
122+
await token.waitForDeployment();
123+
124+
console.log("Upgradeable ERC721 deployed to:", await token.getAddress());
125+
}
126+
127+
main().catch(console.error);
128+
```
129+
130+
* **`deployProxy` function**: Deploys the logic contract behind a proxy, calling the initializer function (`initialize`) automatically.
131+
* **`initializer: "initialize"`**: Explicitly specifies which function initializes the contract.
132+
* **`kind: "uups"`**: Specifies using the UUPS proxy pattern.
133+
134+
Deploy your contract:
135+
136+
```bash
137+
npx hardhat run scripts/deploy-upgradeable.js --network testnet
138+
```
139+
140+
**Make sure to copy the smart contract address for your ERC-721 token.**
141+
142+
<pre><code><strong>// output
143+
</strong><strong>Compiled 32 Solidity files successfully (evm target: paris).
144+
</strong>Upgradeable ERC721 deployed to: 0xb54c97235A7a90004fEb89dDccd68f36066fea8c
145+
</code></pre>
146+
147+
## Step 4: Upgrade Your ERC-721 Contract
148+
149+
Let's upgrade your contract by adding a new `version` function. Create `erc-721-upgrade-v2.sol` in your `contracts` folder:
150+
151+
```solidity
152+
// SPDX-License-Identifier: MIT
153+
pragma solidity ^0.8.22;
154+
155+
import "./erc-721-upgrade.sol";
156+
157+
contract MyTokenUpgradeableV2 is MyTokenUpgradeable {
158+
159+
// New function for demonstration
160+
function version() public pure returns (string memory) {
161+
return "v2";
162+
}
163+
}
164+
165+
```
166+
167+
* Adds a simple `version` method to demonstrate the upgrade. Note that we are extending the "MyTokenUpgradeable" contract.
168+
169+
Compile the upgraded version:
170+
171+
```bash
172+
npx hardhat compile
173+
```
174+
175+
## Step 5: Deploy the Upgrade and Verify
176+
177+
Create `upgrade.js` script to upgrade and verify the new functionality:
178+
179+
```javascript
180+
const { ethers, upgrades } = require("hardhat");
181+
182+
async function main() {
183+
const [deployer] = await ethers.getSigners();
184+
185+
console.log("Upgrading contract with the account:", deployer.address);
186+
187+
const MyTokenUpgradeableV2 = await ethers.getContractFactory(
188+
"MyTokenUpgradeableV2"
189+
);
190+
191+
// REPLACE with your deployed proxy contract address
192+
const proxyAddress = "<YOUR-PROXY-CONTRACT-ADDRESS>";
193+
194+
const upgraded = await upgrades.upgradeProxy(
195+
proxyAddress,
196+
MyTokenUpgradeableV2
197+
);
198+
await upgraded.waitForDeployment();
199+
200+
console.log(
201+
"Contract successfully upgraded at:",
202+
await upgraded.getAddress()
203+
);
204+
205+
// Verify the upgrade by calling the new version() function
206+
const contractVersion = await upgraded.version();
207+
console.log("Contract version after upgrade:", contractVersion);
208+
}
209+
210+
main().catch(console.error);
211+
```
212+
213+
* **`upgradeProxy`**: Replaces the logic contract behind your existing proxy with the new version.
214+
* **`proxyAddress`**: Points to the proxy contract that manages storage and delegates calls to logic contracts. Upgrading involves replacing the logic without altering the stored data. **Make sure to replace the proxy contract address with the address you've copied.**
215+
* **Verification step**: Calls the new `version` method to ensure the upgrade succeeded.
216+
217+
Run this upgrade script:
218+
219+
```bash
220+
npx hardhat run scripts/upgrade.js --network testnet
221+
```
222+
223+
Output confirms the upgrade:
224+
225+
```bash
226+
// output
227+
Upgrading contract with the account: 0x7203b2B56CD700e4Df7C2868216e82bCCA225423
228+
Contract successfully upgraded at: 0xb54c97235A7a90004fEb89dDccd68f36066fea8c
229+
Contract version after upgrade: v2
230+
```
231+
232+
## Why Use the UUPS Pattern?
233+
234+
* **Security**: Upgrade functions can be restricted, ensuring only authorized roles can perform upgrades.
235+
* **Data Retention**: Maintains all token balances and stored data during upgrades.
236+
* **Flexibility**: Enables easy updates for new features, improvements, or critical fixes without redeploying a completely new contract.
237+
238+
Congratulations! 🎉 You've successfully implemented and upgraded an ERC-721 smart contract using OpenZeppelin’s UUPS proxy pattern with Hardhat.
239+
240+
***
241+
242+
## Additional Resources
243+
244+
* [Proxy Upgrade Pattern (OpenZeppelin)](https://docs.openzeppelin.com/upgrades-plugins/proxies)
245+
246+
<table data-card-size="large" data-view="cards"><thead><tr><th align="center"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td align="center"><p>Writer: Michiel, Developer Relations Engineer</p><p><a href="https://github.com/michielmulders">GitHub</a> | <a href="https://www.linkedin.com/in/michielmulders/">LinkedIn</a></p></td><td><a href="https://www.linkedin.com/in/michielmulders/">https://www.linkedin.com/in/michielmulders/</a></td></tr><tr><td align="center"><p>Editor: Luis, Sr Software Developer</p><p><a href="https://github.com/acuarica">GitHub</a></p></td><td><a href="https://github.com/acuarica">https://github.com/acuarica</a></td></tr><tr><td align="center"><p>Editor: Krystal, Technical Writer</p><p><a href="https://github.com/theekrystallee">GitHub</a> | <a href="https://x.com/theekrystallee">X</a></p></td><td><a href="https://x.com/theekrystallee">https://x.com/theekrystallee</a></td></tr></tbody></table>

0 commit comments

Comments
 (0)