Deploying Upgradeable Contracts using Transparent Proxy with Hardhat
Introduction to Transparent Proxy and Selector Conflicts
Before diving into the tutorial, let's briefly introduce the concepts of transparent proxy and selector conflicts:
Transparent Proxy
Transparent proxy is a special type of proxy contract that addresses selector conflict issues by separating management functions from user functions. Key features include:
- Separation of Management Functions: The proxy contract contains specific management functions (such as upgrades) that can only be called by the administrator.
- Transparency: For regular users, the existence of the proxy contract is "transparent," allowing them to interact with it as if they were directly interacting with the logic contract.
- Permission Checks: The proxy contract checks the caller's identity on each invocation to determine whether to execute its own functions or delegate the call to the logic contract.
Selector Conflicts
Selector conflicts refer to issues that may arise when there are functions with the same name in both the proxy and logic contracts. For example:
- If both the proxy and logic contracts have a function named
upgrade()
, it becomes unclear which contract's function should be executed when a user callsupgrade()
.
Transparent proxy resolve this issue by:
- Executing the function in the proxy contract (if it exists) when the caller is the administrator.
- Always delegating the call to the logic contract when the caller is not the administrator.
This approach ensures the security of management functions while providing a seamless experience for regular users.
Next, we will learn how to implement and deploy upgradeable contracts using transparent proxy on Conflux eSpace.
Project Setup
- Create a new directory and initialize the project:
mkdir transparent-proxy-demo
cd transparent-proxy-demo
npm init -y
- Install necessary dependencies:
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/hardhat-upgrades @openzeppelin/contracts dotenv
- Initialize the Hardhat project:
npx hardhat
Choose "Create a JavaScript project".
- Configure Hardhat
Edit the hardhat.config.js
file:
require("@nomicfoundation/hardhat-toolbox");
require("@openzeppelin/hardhat-upgrades");
require("dotenv").config();
module.exports = {
solidity: "0.8.24",
networks: {
eSpaceTestnet: {
url: "https://evmtestnet.confluxrpc.com",
accounts: [process.env.PRIVATE_KEY],
},
},
};
- Create a
.env
file and add your private key:
PRIVATE_KEY=your_private_key_here
Writing Smart Contracts
- Create the initial version of the Box contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Box {
uint256 private _value;
event ValueChanged(uint256 value);
function store(uint256 value) public {
_value = value;
emit ValueChanged(value);
}
function retrieve() public view returns (uint256) {
return _value;
}
}
- Create the upgraded BoxV2 contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract BoxV2 {
uint256 private _value;
event ValueChanged(uint256 value);
function store(uint256 value) public {
_value = value;
emit ValueChanged(value);
}
function retrieve() public view returns (uint256) {
return _value;
}
function increment() public {
_value = _value + 1;
emit ValueChanged(_value);
}
}
Deployment Script
Create a deployment script in scripts/deploy.js
:
const { ethers, upgrades } = require("hardhat");
async function main() {
const Box = await ethers.getContractFactory("Box");
console.log("Deploying Box...");
const box = await upgrades.deployProxy(Box, [42], { initializer: "store" });
await box.waitForDeployment();
console.log("Box deployed to:", await box.getAddress());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});