Solidity里一个超硬核的技术——合约升级!区块链上的智能合约一旦部署,默认是“铁打不动”的,但现实中需求总在变,bug也得修,咋办?合约升级就是救星!它能让你在不换地址、不丢数据的情况下,悄悄把合约逻辑更新,简直像给代码换了个新皮肤!咱们会用大白话把合约升级的套路讲透,从最简单的代理模式到Op
Solidity里一个超硬核的技术——合约升级!区块链上的智能合约一旦部署,默认是“铁打不动”的,但现实中需求总在变,bug也得修,咋办?合约升级就是救星!它能让你在不换地址、不丢数据的情况下,悄悄把合约逻辑更新,简直像给代码换了个新皮肤!咱们会用大白话把合约升级的套路讲透,从最简单的代理模式到OpenZeppelin的透明代理和UUPS,再到多签控制升级,配合硬核代码和Hardhat测试,带你一步步实现安全的合约升级。重点是干货,废话少说,直接上技术细节,帮你把合约升级玩得贼6!
先来搞明白几个关键点:
delegatecall调用逻辑合约,逻辑合约的代码在代理的存储上下文执行。咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从基础代理到高级UUPS和多签升级,逐步实现安全的合约升级。
用Hardhat搭建开发环境,写和测试合约。
mkdir upgrade-demo
cd upgrade-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts @openzeppelin/contracts-upgradeable
npm install ethers
初始化Hardhat:
npx hardhat init
选择TypeScript项目,安装依赖:
npm install --save-dev ts-node typescript @types/node @types/mocha
目录结构:
upgrade-demo/
├── contracts/
│ ├── SimpleProxy.sol
│ ├── LogicV1.sol
│ ├── LogicV2.sol
│ ├── TransparentProxy.sol
│ ├── UUPSProxy.sol
│ ├── MultiSigUpgrade.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── Upgrade.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./"
},
"include": ["hardhat.config.ts", "scripts", "test"]
}
hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: "0.8.20",
networks: {
hardhat: {
chainId: 1337,
},
},
};
export default config;
跑本地节点:
npx hardhat node
先搞一个简单的代理合约,弄清楚delegatecall和升级的套路。
contracts/LogicV1.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract LogicV1 {
address public owner;
uint256 public value;
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
}
function setValue(uint256 _value) public {
require(msg.sender == owner, "Only owner");
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
contracts/LogicV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract LogicV2 {
address public owner;
uint256 public value;
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
}
function setValue(uint256 _value) public {
require(msg.sender == owner, "Only owner");
value = _value * 2; // V2 doubles the value
}
function getValue() public view returns (uint256) {
return value;
}
}
contracts/SimpleProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleProxy {
address public implementation;
address public owner;
constructor(address _implementation) {
owner = msg.sender;
implementation = _implementation;
}
function upgrade(address _newImplementation) public {
require(msg.sender == owner, "Only owner");
implementation = _newImplementation;
}
fallback() external payable {
address impl = implementation;
require(impl != address(0), "Implementation not set");
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
receive() external payable {}
}
owner和value,提供initialize、setValue、getValue。setValue将输入值翻倍,存储布局与V1一致。implementation(逻辑合约地址)和owner。upgrade:更新逻辑合约地址,仅owner可调用。fallback:用delegatecall转发调用到implementation,用汇编实现低级调用。owner和value存在代理合约。onlyOwner限制升级权限。implementation不为空。test/Upgrade.test.ts:
import { ethers } from "hardhat";
import { expect } from "chai";
import { SimpleProxy, LogicV1, LogicV2 } from "../typechain-types";
describe("SimpleProxy", function () {
let proxy: SimpleProxy;
let logicV1: LogicV1;
let logicV2: LogicV2;
let owner: any, addr1: any;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const LogicV1Factory = await ethers.getContractFactory("LogicV1");
logicV1 = await LogicV1Factory.deploy();
await logicV1.deployed();
const ProxyFactory = await ethers.getContractFactory("SimpleProxy");
proxy = await ProxyFactory.deploy(logicV1.address);
await proxy.deployed();
const LogicV2Factory = await ethers.getContractFactory("LogicV2");
logicV2 = await LogicV2Factory.deploy();
await logicV2.deployed();
const proxyAsLogicV1 = LogicV1Factory.attach(proxy.address);
await proxyAsLogicV1.initialize(owner.address);
});
it("should initialize correctly", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
expect(await proxyAsLogicV1.owner()).to.equal(owner.address);
});
it("should set and get value", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
expect(await proxyAsLogicV1.getValue()).to.equal(42);
});
it("should restrict setValue to owner", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await expect(proxyAsLogicV1.connect(addr1).setValue(42)).to.be.revertedWith("Only owner");
});
it("should upgrade to LogicV2", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
await proxy.upgrade(logicV2.address);
const proxyAsLogicV2 = await ethers.getContractFactory("LogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(20);
expect(await proxyAsLogicV2.owner()).to.equal(owner.address);
});
it("should restrict upgrade to owner", async function () {
await expect(proxy.connect(addr1).upgrade(logicV2.address)).to.be.revertedWith("Only owner");
});
});
跑测试:
npx hardhat test
LogicV1和SimpleProxy,通过代理调用initialize。value为42,验证存储在代理。LogicV2,setValue(10)返回20,owner不变。owner无法升级。owner和value,delegatecall确保逻辑合约操作代理存储。存储布局不一致会导致数据错乱,来看个错误例子。
contracts/BadLogicV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract BadLogicV2 {
uint256 public value; // Slot 0 (wrong order)
address public owner; // Slot 1 (wrong order)
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
}
function setValue(uint256 _value) public {
require(msg.sender == owner, "Only owner");
value = _value * 2;
}
function getValue() public view returns (uint256) {
return value;
}
}
测试:
test/Upgrade.test.ts(添加):
it("should fail with storage collision", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
const BadLogicV2Factory = await ethers.getContractFactory("BadLogicV2");
const badLogicV2 = await BadLogicV2Factory.deploy();
await badLogicV2.deployed();
await proxy.upgrade(badLogicV2.address);
const proxyAsBadLogicV2 = BadLogicV2Factory.attach(proxy.address);
expect(await proxyAsBadLogicV2.owner()).to.not.equal(owner.address);
});
LogicV1的owner(slot 0), value(slot 1),BadLogicV2的value(slot 0), owner(slot 1),升级后owner被覆盖为value的值。用OpenZeppelin的透明代理,解决管理员和用户调用冲突。
contracts/LogicV1.sol(更新):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract LogicV1 is Initializable, OwnableUpgradeable {
uint256 public value;
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
contracts/LogicV2.sol(更新):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract LogicV2 is Initializable, OwnableUpgradeable {
uint256 public value;
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
}
function setValue(uint256 _value) public onlyOwner {
value = _value * 2;
}
function getValue() public view returns (uint256) {
return value;
}
}
contracts/TransparentProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract TransparentProxy is TransparentUpgradeableProxy {
constructor(address logic, address admin, bytes memory data)
TransparentUpgradeableProxy(logic, admin, data)
{}
}
Initializable和OwnableUpgradeable,支持代理初始化。initializer确保初始化只执行一次。TransparentUpgradeableProxy。OwnableUpgradeable管理权限。test/Upgrade.test.ts(更新):
import { ethers } from "hardhat";
import { expect } from "chai";
import { TransparentProxy, LogicV1, LogicV2 } from "../typechain-types";
describe("TransparentProxy", function () {
let proxy: TransparentProxy;
let logicV1: LogicV1;
let logicV2: LogicV2;
let owner: any, addr1: any, admin: any;
beforeEach(async function () {
[owner, addr1, admin] = await ethers.getSigners();
const LogicV1Factory = await ethers.getContractFactory("LogicV1");
logicV1 = await LogicV1Factory.deploy();
await logicV1.deployed();
const ProxyFactory = await ethers.getContractFactory("TransparentProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
proxy = await ProxyFactory.deploy(logicV1.address, admin.address, initData);
await proxy.deployed();
const LogicV2Factory = await ethers.getContractFactory("LogicV2");
logicV2 = await LogicV2Factory.deploy();
await logicV2.deployed();
});
it("should initialize correctly", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
expect(await proxyAsLogicV1.owner()).to.equal(owner.address);
});
it("should set and get value", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
expect(await proxyAsLogicV1.getValue()).to.equal(42);
});
it("should restrict setValue to owner", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await expect(proxyAsLogicV1.connect(addr1).setValue(42)).to.be.revertedWith("Ownable: caller is not the owner");
});
it("should upgrade to LogicV2", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("LogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
const ProxyAdminFactory = await ethers.getContractFactory("ProxyAdmin", admin);
const proxyAdmin = await ProxyAdminFactory.deploy();
await proxyAdmin.deployed();
await proxyAdmin.connect(admin).upgrade(proxy.address, logicV2.address);
const proxyAsLogicV2 = await ethers.getContractFactory("LogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(20);
expect(await proxyAsLogicV2.owner()).to.equal(owner.address);
});
it("should restrict upgrade to admin", async function () {
const ProxyAdminFactory = await ethers.getContractFactory("ProxyAdmin", admin);
const proxyAdmin = await ProxyAdminFactory.deploy();
await proxyAdmin.deployed();
await expect(proxyAdmin.connect(addr1).upgrade(proxy.address, logicV2.address)).to.be.revertedWith("Ownable: caller is not the owner");
});
});
LogicV1和TransparentProxy,通过initData初始化。value为42。ProxyAdmin切换到LogicV2,setValue(10)返回20。admin可升级。UUPS把升级逻辑放逻辑合约,代理更轻量。
contracts/UUPSLogicV1.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract UUPSLogicV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public value;
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
contracts/UUPSLogicV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract UUPSLogicV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public value;
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
}
function setValue(uint256 _value) public onlyOwner {
value = _value * 2;
}
function getValue() public view returns (uint256) {
return value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
contracts/UUPSProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract UUPSProxy is ERC1967Proxy {
constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) {}
}
UUPSUpgradeable,包含升级逻辑。_authorizeUpgrade:限制升级权限。ERC1967Proxy,逻辑地址存标准槽位。onlyOwner控制升级。Initializer防止重复初始化。test/Upgrade.test.ts(更新):
import { ethers } from "hardhat";
import { expect } from "chai";
import { UUPSProxy, UUPSLogicV1, UUPSLogicV2 } from "../typechain-types";
describe("UUPSProxy", function () {
let proxy: UUPSProxy;
let logicV1: UUPSLogicV1;
let logicV2: UUPSLogicV2;
let owner: any, addr1: any;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const LogicV1Factory = await ethers.getContractFactory("UUPSLogicV1");
logicV1 = await LogicV1Factory.deploy();
await logicV1.deployed();
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
proxy = await ProxyFactory.deploy(logicV1.address, initData);
await proxy.deployed();
const LogicV2Factory = await ethers.getContractFactory("UUPSLogicV2");
logicV2 = await LogicV2Factory.deploy();
await logicV2.deployed();
});
it("should initialize correctly", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("UUPSLogicV1").then(f => f.attach(proxy.address));
expect(await proxyAsLogicV1.owner()).to.equal(owner.address);
});
it("should set and get value", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("UUPSLogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
expect(await proxyAsLogicV1.getValue()).to.equal(42);
});
it("should upgrade to LogicV2", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("UUPSLogicV1").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
await proxyAsLogicV1.upgradeTo(logicV2.address);
const proxyAsLogicV2 = await ethers.getContractFactory("UUPSLogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(20);
expect(await proxyAsLogicV2.owner()).to.equal(owner.address);
});
it("should restrict upgrade to owner", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("UUPSLogicV1").then(f => f.attach(proxy.address));
await expect(proxyAsLogicV1.connect(addr1).upgradeTo(logicV2.address)).to.be.revertedWith("Ownable: caller is not the owner");
});
});
UUPSLogicV1和UUPSProxy,通过initData初始化。value为42。upgradeTo切换到LogicV2,setValue(10)返回20。owner无法升级。为升级加多签机制,需多人同意。
contracts/MultiSigUpgrade.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MultiSigUpgrade is Initializable, UUPSUpgradeable, OwnableUpgradeable {
address[] public owners;
uint256 public required;
uint256 public transactionCount;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
uint256 public value;
struct Transaction {
address newImplementation;
bool executed;
uint256 confirmationCount;
}
event SubmitUpgrade(uint256 indexed txId, address indexed newImplementation);
event ConfirmUpgrade(uint256 indexed txId, address indexed owner);
event ExecuteUpgrade(uint256 indexed txId, address indexed newImplementation);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
modifier onlyOwner() {
bool isOwner = false;
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] == msg.sender) {
isOwner = true;
break;
}
}
require(isOwner, "Not owner");
_;
}
function initialize(address[] memory _owners, uint256 _required) public initializer {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required");
owners = _owners;
required = _required;
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
function submitUpgrade(address newImplementation) public onlyOwner {
require(newImplementation != address(0), "Invalid implementation");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
newImplementation: newImplementation,
executed: false,
confirmationCount: 0
});
emit SubmitUpgrade(txId, newImplementation);
}
function confirmUpgrade(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(!confirmations[txId][msg.sender], "Already confirmed");
confirmations[txId][msg.sender] = true;
transaction.confirmationCount++;
emit ConfirmUpgrade(txId, msg.sender);
if (transaction.confirmationCount >= required) {
executeUpgrade(txId);
}
}
function executeUpgrade(uint256 txId) internal {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmationCount >= required, "Insufficient confirmations");
transaction.executed = true;
_authorizeUpgrade(transaction.newImplementation);
_upgradeTo(transaction.newImplementation);
emit ExecuteUpgrade(txId, transaction.newImplementation);
}
function revokeConfirmation(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(confirmations[txId][msg.sender], "Not confirmed");
confirmations[txId][msg.sender] = false;
transaction.confirmationCount--;
emit RevokeConfirmation(txId, msg.sender);
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
contracts/MultiSigLogicV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MultiSigLogicV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
address[] public owners;
uint256 public required;
uint256 public transactionCount;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
uint256 public value;
struct Transaction {
address newImplementation;
bool executed;
uint256 confirmationCount;
}
event SubmitUpgrade(uint256 indexed txId, address indexed newImplementation);
event ConfirmUpgrade(uint256 indexed txId, address indexed owner);
event ExecuteUpgrade(uint256 indexed txId, address indexed newImplementation);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
modifier onlyOwner() {
bool isOwner = false;
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] == msg.sender) {
isOwner = true;
break;
}
}
require(isOwner, "Not owner");
_;
}
function initialize(address[] memory _owners, uint256 _required) public initializer {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required");
owners = _owners;
required = _required;
}
function setValue(uint256 _value) public onlyOwner {
value = _value * 2; // V2 doubles the value
}
function getValue() public view returns (uint256) {
return value;
}
function submitUpgrade(address newImplementation) public onlyOwner {
require(newImplementation != address(0), "Invalid implementation");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
newImplementation: newImplementation,
executed: false,
confirmationCount: 0
});
emit SubmitUpgrade(txId, newImplementation);
}
function confirmUpgrade(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(!confirmations[txId][msg.sender], "Already confirmed");
confirmations[txId][msg.sender] = true;
transaction.confirmationCount++;
emit ConfirmUpgrade(txId, msg.sender);
if (transaction.confirmationCount >= required) {
executeUpgrade(txId);
}
}
function executeUpgrade(uint256 txId) internal {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmationCount >= required, "Insufficient confirmations");
transaction.executed = true;
_authorizeUpgrade(transaction.newImplementation);
_upgradeTo(transaction.newImplementation);
emit ExecuteUpgrade(txId, transaction.newImplementation);
}
function revokeConfirmation(uint256 txId) public onlyOwner {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Transaction already executed");
require(confirmations[txId][msg.sender], "Not confirmed");
confirmations[txId][msg.sender] = false;
transaction.confirmationCount--;
emit RevokeConfirmation(txId, msg.sender);
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
UUPSUpgradeable,支持多签升级。owners和required控制多签。submitUpgrade:提交升级提案。confirmUpgrade:确认提案,达标后执行。executeUpgrade:调用_upgradeTo切换逻辑。revokeConfirmation:撤销确认。setValue翻倍。onlyOwner限制操作。newImplementation有效性。test/Upgrade.test.ts(添加):
import { ethers } from "hardhat";
import { expect } from "chai";
import { UUPSProxy, MultiSigUpgrade, MultiSigLogicV2 } from "../typechain-types";
describe("MultiSigUpgrade", function () {
let proxy: UUPSProxy;
let logicV1: MultiSigUpgrade;
let logicV2: MultiSigLogicV2;
let owner1: any, owner2: any, owner3: any, nonOwner: any;
beforeEach(async function () {
[owner1, owner2, owner3, nonOwner] = await ethers.getSigners();
const LogicV1Factory = await ethers.getContractFactory("MultiSigUpgrade");
logicV1 = await LogicV1Factory.deploy();
await logicV1.deployed();
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [
[owner1.address, owner2.address, owner3.address],
2,
]);
proxy = await ProxyFactory.deploy(logicV1.address, initData);
await proxy.deployed();
const LogicV2Factory = await ethers.getContractFactory("MultiSigLogicV2");
logicV2 = await LogicV2Factory.deploy();
await logicV2.deployed();
});
it("should initialize correctly", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
expect(await proxyAsLogicV1.owners(0)).to.equal(owner1.address);
expect(await proxyAsLogicV1.required()).to.equal(2);
});
it("should set and get value", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
expect(await proxyAsLogicV1.getValue()).to.equal(42);
});
it("should upgrade with multi-sig", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.setValue(42);
await proxyAsLogicV1.submitUpgrade(logicV2.address);
await proxyAsLogicV1.connect(owner2).confirmUpgrade(0);
await proxyAsLogicV1.connect(owner3).confirmUpgrade(0);
const proxyAsLogicV2 = await ethers.getContractFactory("MultiSigLogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(20);
expect(await proxyAsLogicV2.owners(0)).to.equal(owner1.address);
});
it("should not upgrade without enough confirmations", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.submitUpgrade(logicV2.address);
await proxyAsLogicV1.connect(owner2).confirmUpgrade(0);
const proxyAsLogicV2 = await ethers.getContractFactory("MultiSigLogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(10); // Still V1
});
it("should allow revoking confirmation", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.submitUpgrade(logicV2.address);
await proxyAsLogicV1.connect(owner2).confirmUpgrade(0);
await proxyAsLogicV1.connect(owner2).revokeConfirmation(0);
await proxyAsLogicV1.connect(owner3).confirmUpgrade(0);
const proxyAsLogicV2 = await ethers.getContractFactory("MultiSigLogicV2").then(f => f.attach(proxy.address));
expect(await proxyAsLogicV2.getValue()).to.equal(0); // Still V1
});
it("should restrict upgrade to owners", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await expect(proxyAsLogicV1.connect(nonOwner).submitUpgrade(logicV2.address)).to.be.revertedWith("Not owner");
});
});
owners,需2人确认。value为42。LogicV2,setValue(10)返回20。scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [owner, admin, owner1, owner2, owner3] = await ethers.getSigners();
const LogicV1Factory = await ethers.getContractFactory("LogicV1");
const logicV1 = await LogicV1Factory.deploy();
await logicV1.deployed();
console.log(`LogicV1 deployed to: ${logicV1.address}`);
const TransparentProxyFactory = await ethers.getContractFactory("TransparentProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
const transparentProxy = await TransparentProxyFactory.deploy(logicV1.address, admin.address, initData);
await transparentProxy.deployed();
console.log(`TransparentProxy deployed to: ${transparentProxy.address}`);
const UUPSLogicV1Factory = await ethers.getContractFactory("UUPSLogicV1");
const uupsLogicV1 = await UUPSLogicV1Factory.deploy();
await uupsLogicV1.deployed();
console.log(`UUPSLogicV1 deployed to: ${uupsLogicV1.address}`);
const UUPSProxyFactory = await ethers.getContractFactory("UUPSProxy");
const uupsProxy = await UUPSProxyFactory.deploy(uupsLogicV1.address, initData);
await uupsProxy.deployed();
console.log(`UUPSProxy deployed to: ${uupsProxy.address}`);
const MultiSigLogicV1Factory = await ethers.getContractFactory("MultiSigUpgrade");
const multiSigLogicV1 = await MultiSigLogicV1Factory.deploy();
await multiSigLogicV1.deployed();
console.log(`MultiSigUpgrade deployed to: ${multiSigLogicV1.address}`);
const multiSigInitData = MultiSigLogicV1Factory.interface.encodeFunctionData("initialize", [
[owner1.address, owner2.address, owner3.address],
2,
]);
const multiSigProxy = await UUPSProxyFactory.deploy(multiSigLogicV1.address, multiSigInitData);
await multiSigProxy.deployed();
console.log(`MultiSigProxy deployed to: ${multiSigProxy.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
npx hardhat run scripts/deploy.ts --network hardhat
SimpleProxy:~0.6M gas。TransparentProxy:~1.2M gas。UUPSProxy:~0.8M gas。LogicV1/UUPSLogicV1:~1M gas。MultiSigUpgrade:~1.5M gas。跑代码,体验Solidity合约升级的硬核玩法吧!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!