合约升级方案
目前主流的可升级方案大多都是使用delegatecall
和fallback
,部署一个Proxy
合约的方式来实现。
使用openzeppelin
的@openzeppelin/hardhat-upgrades
库
hardhat 中集成
- 安装依赖
yarn add -D @openzeppelin/hardhat-upgrades @nomiclabs/hardhat-ethers ethers
- 在
hardhat.config
配置文件中导入依赖
如果是 JS
// hardhat.config.js
require('@openzeppelin/hardhat-upgrades');
如果是 TS
// hardhat.config.ts
import '@openzeppelin/hardhat-upgrades';
- 示例合约
注意: 可升级合约的部署不会调用constructor
方法。所以实现一个initialize
方法来做合约初始化
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.2;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract TodoList is Initializable {
string[] private list;
address public admin;
event AddTodo(address indexed author, string item);
function initialize(address _admin) external initializer {
admin = _admin;
}
function addTodo(string memory message) external {
list.push(message);
emit AddTodo(msg.sender, message);
}
function getTodoList() external view returns (string[] memory totoList) {
totoList = list;
}
// 相比V1 增加了获取列表长度的方法
// function getTodoListLength() external view returns(uint256) {
// return list.length;
// }
}
- 第一次部署合约
async function main() {
// 第一次部署合约
const signers = await ethers.getSigners();
const admin = signers[0];
const TodoList = await ethers.getContractFactory("TodoList");
// 可以部署的时候进行初始化传参
// const instance = await upgrades.deployProxy(TodoList, [admin.address]);
// 也可以先部署,然后调用初始化方法.这里要禁止插件自动调用初始化方法
const instance = await upgrades.deployProxy(TodoList, { initializer: false });
await instance.initialize(admin.address);
await instance.deployed();
console.log(`proxy deployed: ${instance.address}`);
}
- 测试
注意,这里本地测试的时候要加上--network localhost
,否则每次都会生成一个新的网络节点
npx hardhat run scripts/deploy.ts --network localhost
其他 truffle
等集成方案可以参考Upgrades Plugins
注意 ProxyAdmin
当我们通过插件部署一个新的合约时,插件会同时部署一个ProxyAdmin
合约,用于管理owner
和admin
。ProxyAdmin
的初始owner
就是部署者账号.owner
和admin
的区别如下:
- owner: 拥有合约的升级和交互权限
- admin: 只拥有合约的升级权限,没有合约的交互权限
可以通过以下方法转移owner
或admin
admin.changeProxyAdmin
: 这个方法可以修改合约的admin
admin.transferProxyAdminOwnership
: 可以将ProxyAdmin
的owner
权限转移给一个新的账号。则新的账号会拥有升级和交互权限,所以调用时要谨慎;当我们把owner
权限转移出去以后,依然可以使用当前账号执行prepareUpgrade
方法,用于验证新升级的合约是否与老的兼容,然后由真正的owner
执行升级
具体参考:
注意: 新的升级合约要保证不会破坏老合约插槽分部,否则会发生数据错乱