在Conflux eSpace上使用Hardhat部署可升级合约
在深入学习教程之前,我们先简要解释一下实现可升级合约的基本原理:
- 关注点分离: 合约逻辑与存储通过两个合约进行分离。
- A Proxy contract that holds the state and receives user interactions.
- A Logic contract (Implementation contract) that contains the actual code logic.
-
委托调用:代理合约使用
delegatecall
将函数调用转发到逻辑合约。 -
可升级性: 通过部署一个新的逻辑合约并更新代理指向它来实现升级。
-
回退函数: 代理合约使用回退函数捕获并委托所有函数调用
-
Storage Layout: Ensure new versions of the Logic contract maintain the same storage layout to prevent data corruption.
可升级合约的工作流程如下:
- 代理合约存储当前逻辑合约的地址。
- When the Proxy is called, it triggers the fallback function.
- The fallback function uses
delegatecall
to forward the call to the Logic contract. - The Logic contract executes the function in the context of the Proxy's storage.
- To upgrade, deploy a new Logic contract and update the Proxy's reference.
This pattern allows for upgrading contract logic while preserving the contract's state and address, providing a seamless experience for users and other contracts interacting with the upgradeable contract.
Next, we'll proceed with the tutorial on how to implement this pattern on Conflux eSpace using Hardhat.
1. 项目设置
首先,确保你已经安装了 Node.js 和 npm。 然后,创建一个新目录并初始化项目:
mkdir upgradeable-contract-demo
cd upgradeable-contract-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
接下来,初始化Hardhat项目并选择JavaScript默认模板:
npx hardhat
When prompted, choose "Create a JavaScript project". This will create a basic Hardhat project structure, including contracts
, scripts
, and test
directories, as well as a default hardhat.config.js
file.
After completing these steps, you'll have a basic Hardhat project structure using JavaScript, ready for writing and deploying upgradeable contracts.
2. 配置Hardhat
创建Hardhat配置文件:
require("@nomicfoundation/hardhat-toolbox");
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
3. 编写智能合约
Create a contracts directory and add the following contracts:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SimpleUpgrade {
// Address of the current implementation contract
address public implementation;
// Address of the admin who can upgrade the contract
address public admin;
// A string variable to demonstrate state changes
string public words;
// Constructor sets the initial implementation and admin
constructor(address _implementation) {
admin = msg.sender;
implementation = _implementation;
}
// Fallback function to delegate calls to the implementation contract
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success, "Delegatecall failed");
}
// Receive function to accept Ether
receive() external payable {
}
// Function to upgrade the implementation contract
// Only the admin can call this function
function upgrade(address newImplementation) external {
require(msg.sender == admin, "Only admin can upgrade");
implementation = newImplementation;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Logic1 {
// Address of the current implementation contract
address public implementation;
// Address of the admin who can upgrade the contract
address public admin;
// A string variable to demonstrate state changes
string public words;
// Function to set the 'words' variable
function foo() public {
words = "old";
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Logic2 {
// Address of the current implementation contract
address public implementation;
// Address of the admin who can upgrade the contract
address public admin;
// A string variable to demonstrate state changes
string public words;
// Function to set the 'words' variable
// Note: This function is different from Logic1
function foo() public {
words = "new";
}
}
4. 部署脚本
Create a scripts directory and add the following script:
const hre = require("hardhat");
async function main() {
// Deploy Logic1 contract
const Logic1 = await hre.ethers.getContractFactory("Logic1");
const logic1 = await Logic1.deploy();
await logic1.waitForDeployment();
console.log("Logic1 deployed to:", await logic1.getAddress());
// Deploy SimpleUpgrade (Proxy) contract
const SimpleUpgrade = await hre.ethers.getContractFactory("SimpleUpgrade");
const proxy = await SimpleUpgrade.deploy(await logic1.getAddress());
await proxy.waitForDeployment();
console.log("Proxy deployed to:", await proxy.getAddress());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});