openzeppelin可升级合约
安装 @openzeppelin/truffle-upgrades
npm i @openzeppelin/truffle-upgrades
编写合约和脚本
为了更加清晰的认识可升级合约,这里一次部署两个合约
// contracts/Test1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test1 {
function get() external pure returns(uint a) {
a = 1;
}
uint public x;
function add() external {
x++;
}
}
// contracts/Test2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test1 {
function get() external pure returns(uint a) {
a = 1;
}
uint public x;
function add() external {
x++;
}
}
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
const Test1 = artifacts.require("Test1");
const Test2 = artifacts.require("Test2");
module.exports = async function (deployer) {
await deployProxy(Test1);
await deployProxy(Test2);
};
部署、验证合约
truffle migrate --network ropsten
truffle run verify Test1 Test2 --network ropsten
可升级合约解析
简单介绍
通过刚才的部署,可以看到我们部署了5个合约,分别是 :
Test1:我们编写的 Test1 合约
TransparentUpgradeableProxy:Test1 的代理合约
Test2:我们编写的 Test 合约
TransparentUpgradeableProxy:Test2 的代理合约
ProxyAdmin:管理员合约,用于管理所有代理合约,后续 Test1、Test2 合约升级就是改变对应代理合约的指向。
这便可以清晰的认识到,我们编写的每个合约,都会对应一个代理合约,
发布两个合约的作用已经起到,后面讲解便忽略 Test2 合约
ProxyAdmin 合约
三个读函数
getProxyAdmin:获取代理合约管理员地址,输入 Test1 代理合约地址,返回本合约(ProxyAdmin )地址
getProxyImplementation :获取代理合约实现合约地址,输入 Test1 代理合约地址,指向 Test1 合约地址
owner:合约owner,部署合约的地址,也就是项目网络配置中 mnemonic 对应的账户地址
五个写函数,
1.更换代理合约管理员
2.放弃合约所有权
3.更换合约owner
4.升级合约
5.升级并调用合约
TransparentUpgradeableProxy:Test1 的代理合约
该合约没有读合约,略过,看写合约,五个函数:
1.管理员:该函数实际上是一个读函数,但函数并没有加 view 关键字,并且加了 ifAdmin 修改器,只有管理员可以访问,一定程度上起到了保护 admin 合约地址的作用
2.变更管理员:需要admin地址访问
3.代理实现:同admin函数
4.升级合约:需要admin地址访问
5.升级并调用合约:需要admin地址访问
综上所述,这五个函数都是为管理员合约提供的专用函数,无法直接访问
后面的代理函数,则是我们 Test1 合约的函数,先调用写合约add函数,再查询结果
合约升级
工作原理
熟悉委托调用的朋友都知道,合约A中函数委托调用合约B中函数,只是使用合约B中函数的逻辑,改变的是合约A中的变量,这里代理合约便是委托调用我们编写的逻辑合约,所以合约升级只是更换我们的逻辑合约,原数据还在代理合约中不会丢失。接下来顺便验证这一点
修改合约 和脚本
// contracts/Test1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test1 {
function get() external pure returns(uint a) {
a = 10;
}
uint public x;
function add() external {
x+= 10;
}
}
deployProxy部署合约会部署一个代理合约和一个逻辑合约,
升级合约只部署新的逻辑合约并修改指向便可,采用普通的部署方式
const Test1 = artifacts.require("Test1");
module.exports = async function (deployer) {
deployer.deploy(Test1);
};
部署验证略过
修改代理实现
调用 ProxyAdmin 合约 upgrade 函数
第一个参数为 TransparentUpgradeableProxy 合约地址
第二个参数为 刚刚部署的 Test1 合约地址
需要连接 ProxyAdmin 合约的owner地址
测试
查看 TransparentUpgradeableProxy 合约
x 的值还是1 并没有丢失
调用add方法后 再次查看 x 的值变为11,说明我们合约已经升级成功。
验证
查看新部署的Test1合约,x的值依旧是0,验证了代理合约只是使用逻辑合约的逻辑,改变了代理合约的变量,逻辑合约内变量没有变化