Solidity里一个超硬核的主题——合约版本控制!以太坊智能合约一旦部署,默认是“铁打不动”,但现实中业务需求总在变,bug也得修,咋办?版本控制就是救星!它能让你在不换地址、不丢数据的情况下,把合约逻辑升级得像换皮肤一样丝滑!这篇干货会用大白话把Solidity的版本控制技巧讲得明明白白,从基础
Solidity里一个超硬核的主题——合约版本控制!以太坊智能合约一旦部署,默认是“铁打不动”,但现实中业务需求总在变,bug也得修,咋办?版本控制就是救星!它能让你在不换地址、不丢数据的情况下,把合约逻辑升级得像换皮肤一样丝滑!这篇干货会用大白话把Solidity的版本控制技巧讲得明明白白,从基础代理模式到OpenZeppelin的透明代理、UUPS,再到多签控制和事件追踪,配合Hardhat测试和完整代码,带你一步步打造稳如老狗的版本控制方案。重点是硬核知识点,废话少说,直接上技术细节,帮你把合约升级玩得贼6!
先搞清楚几个关键点:
delegatecall调用逻辑合约,逻辑合约的代码在代理的存储上下文执行。咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从简单代理到多签升级,逐步实现安全的版本控制。
用Hardhat搭建开发环境,写和测试合约。
mkdir version-control-demo
cd version-control-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
目录结构:
version-control-demo/
├── contracts/
│ ├── SimpleProxy.sol
│ ├── LogicV1.sol
│ ├── LogicV2.sol
│ ├── TransparentProxy.sol
│ ├── UUPSProxy.sol
│ ├── MultiSigUpgrade.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── VersionControl.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;
uint256 public version;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
version = 1;
}
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;
uint256 public version;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
version = 2;
}
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;
uint256 public version;
mapping(uint256 => address) public versionHistory;
event Upgraded(address indexed implementation, uint256 version);
constructor(address _implementation) {
owner = msg.sender;
implementation = _implementation;
version = 1;
versionHistory[1] = _implementation;
emit Upgraded(_implementation, 1);
}
function upgrade(address _newImplementation) public {
require(msg.sender == owner, "Only owner");
implementation = _newImplementation;
version++;
versionHistory[version] = _newImplementation;
emit Upgraded(_newImplementation, version);
}
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和version,提供initialize、setValue、getValue,通过Upgraded事件记录版本。setValue将输入值翻倍,存储布局与V1一致,version设为2。implementation(逻辑合约地址)、owner、version和versionHistory(版本历史)。upgrade:更新逻辑合约地址,增加版本号,记录历史,仅owner可调用。fallback:用delegatecall转发调用到implementation,用汇编实现低级调用。Upgraded事件:记录每次升级的地址和版本号。owner、value和version存在代理合约。onlyOwner限制升级权限。implementation不为空。test/VersionControl.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);
expect(await proxyAsLogicV1.version()).to.equal(1);
expect(await proxy.version()).to.equal(1);
expect(await proxy.versionHistory(1)).to.equal(logicV1.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 expect(proxy.upgrade(logicV2.address))
.to.emit(proxy, "Upgraded")
.withArgs(logicV2.address, 2);
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);
expect(await proxyAsLogicV2.version()).to.equal(2);
expect(await proxy.versionHistory(2)).to.equal(logicV2.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和version保持正确。versionHistory记录逻辑合约地址,Upgraded事件追踪升级。owner无法升级。owner、value、version,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)
uint256 public version;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
version = 2;
}
function setValue(uint256 _value) public {
require(msg.sender == owner, "Only owner");
value = _value * 2;
}
function getValue() public view returns (uint256) {
return value;
}
}
测试:
test/VersionControl.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), version(slot 2),BadLogicV2的value(slot 0), owner(slot 1), version(slot 2),升级后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;
uint256 public version;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
version = 1;
emit Upgraded(address(this), 1);
}
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;
uint256 public version;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
version = 2;
emit Upgraded(address(this), 2);
}
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";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
contract TransparentProxy is TransparentUpgradeableProxy {
constructor(address logic, address admin, bytes memory data)
TransparentUpgradeableProxy(logic, admin, data)
{}
}
Initializable和OwnableUpgradeable,支持代理初始化。initializer确保初始化只执行一次。Upgraded事件记录版本和逻辑地址。TransparentUpgradeableProxy。OwnableUpgradeable管理权限。test/VersionControl.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 proxyAdmin: any;
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 ProxyAdminFactory = await ethers.getContractFactory("ProxyAdmin", admin);
proxyAdmin = await ProxyAdminFactory.deploy();
await proxyAdmin.deployed();
const ProxyFactory = await ethers.getContractFactory("TransparentProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
proxy = await ProxyFactory.deploy(logicV1.address, proxyAdmin.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);
expect(await proxyAsLogicV1.version()).to.equal(1);
});
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);
await expect(proxyAdmin.connect(admin).upgrade(proxy.address, logicV2.address))
.to.emit(proxy, "Upgraded")
.withArgs(logicV2.address, 2);
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);
expect(await proxyAsLogicV2.version()).to.equal(2);
});
it("should restrict upgrade to admin", async function () {
await expect(proxyAdmin.connect(addr1).upgrade(proxy.address, logicV2.address)).to.be.revertedWith("Ownable: caller is not the owner");
});
});
LogicV1、ProxyAdmin和TransparentProxy,通过initData初始化。value为42。ProxyAdmin切换到LogicV2,setValue(10)返回20,version更新为2。admin可升级。Upgraded事件记录升级历史。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;
uint256 public version;
mapping(uint256 => address) public versionHistory;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
version = 1;
versionHistory[1] = address(this);
emit Upgraded(address(this), 1);
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
version++;
versionHistory[version] = newImplementation;
emit Upgraded(newImplementation, version);
}
}
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;
uint256 public version;
mapping(uint256 => address) public versionHistory;
event Upgraded(address indexed implementation, uint256 version);
function initialize(address _owner) public initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
version = 2;
versionHistory[2] = address(this);
emit Upgraded(address(this), 2);
}
function setValue(uint256 _value) public onlyOwner {
value = _value * 2;
}
function getValue() public view returns (uint256) {
return value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
version++;
versionHistory[version] = newImplementation;
emit Upgraded(newImplementation, version);
}
}
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:限制升级权限,更新version和versionHistory。Upgraded事件记录版本历史。ERC1967Proxy,逻辑地址存标准槽位。onlyOwner控制升级。Initializer防止重复初始化。test/VersionControl.test.ts(update):
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);
expect(await proxyAsLogicV1.version()).to.equal(1);
expect(await proxyAsLogicV1.versionHistory(1)).to.equal(logicV1.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 expect(proxyAsLogicV1.upgradeTo(logicV2.address))
.to.emit(proxyAsLogicV1, "Upgraded")
.withArgs(logicV2.address, 2);
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);
expect(await proxyAsLogicV2.version()).to.equal(2);
expect(await proxyAsLogicV2.versionHistory(2)).to.equal(logicV2.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,version更新。versionHistory和Upgraded事件记录升级历史。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;
uint256 public version;
mapping(uint256 => address) public versionHistory;
struct Transaction {
address newImplementation;
bool executed;
uint256 confirmationCount;
uint256 version;
}
event SubmitUpgrade(uint256 indexed txId, address indexed newImplementation, uint256 version);
event ConfirmUpgrade(uint256 indexed txId, address indexed owner);
event ExecuteUpgrade(uint256 indexed txId, address indexed newImplementation, uint256 version);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
event Upgraded(address indexed implementation, uint256 version);
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;
version = 1;
versionHistory[1] = address(this);
emit Upgraded(address(this), 1);
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
function submitUpgrade(address newImplementation, uint256 newVersion) public onlyOwner {
require(newImplementation != address(0), "Invalid implementation");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
newImplementation: newImplementation,
executed: false,
confirmationCount: 0,
version: newVersion
});
emit SubmitUpgrade(txId, newImplementation, newVersion);
}
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;
version = transaction.version;
versionHistory[version] = transaction.newImplementation;
_authorizeUpgrade(transaction.newImplementation);
_upgradeTo(transaction.newImplementation);
emit ExecuteUpgrade(txId, transaction.newImplementation, version);
emit Upgraded(transaction.newImplementation, version);
}
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;
uint256 public version;
mapping(uint256 => address) public versionHistory;
struct Transaction {
address newImplementation;
bool executed;
uint256 confirmationCount;
uint256 version;
}
event SubmitUpgrade(uint256 indexed txId, address indexed newImplementation, uint256 version);
event ConfirmUpgrade(uint256 indexed txId, address indexed owner);
event ExecuteUpgrade(uint256 indexed txId, address indexed newImplementation, uint256 version);
event RevokeConfirmation(uint256 indexed txId, address indexed owner);
event Upgraded(address indexed implementation, uint256 version);
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;
version = 2;
versionHistory[2] = address(this);
emit Upgraded(address(this), 2);
}
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, uint256 newVersion) public onlyOwner {
require(newImplementation != address(0), "Invalid implementation");
uint256 txId = transactionCount++;
transactions[txId] = Transaction({
newImplementation: newImplementation,
executed: false,
confirmationCount: 0,
version: newVersion
});
emit SubmitUpgrade(txId, newImplementation, newVersion);
}
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;
version = transaction.version;
versionHistory[version] = transaction.newImplementation;
_authorizeUpgrade(transaction.newImplementation);
_upgradeTo(transaction.newImplementation);
emit ExecuteUpgrade(txId, transaction.newImplementation, version);
emit Upgraded(transaction.newImplementation, version);
}
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切换逻辑,更新version和versionHistory。revokeConfirmation:撤销确认。Upgraded事件记录版本历史。setValue翻倍,保持多签逻辑。onlyOwner限制操作。newImplementation有效性。test/VersionControl.test.ts(add):
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);
expect(await proxyAsLogicV1.version()).to.equal(1);
expect(await proxyAsLogicV1.versionHistory(1)).to.equal(logicV1.address);
});
it("should set and get value", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.connect(owner1).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.connect(owner1).setValue(42);
await proxyAsLogicV1.connect(owner1).submitUpgrade(logicV2.address, 2);
await proxyAsLogicV1.connect(owner2).confirmUpgrade(0);
await expect(proxyAsLogicV1.connect(owner3).confirmUpgrade(0))
.to.emit(proxyAsLogicV1, "Upgraded")
.withArgs(logicV2.address, 2);
const proxyAsLogicV2 = await ethers.getContractFactory("MultiSigLogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.connect(owner1).setValue(10);
expect(await proxyAsLogicV2.getValue()).to.equal(20);
expect(await proxyAsLogicV2.owners(0)).to.equal(owner1.address);
expect(await proxyAsLogicV2.version()).to.equal(2);
expect(await proxyAsLogicV2.versionHistory(2)).to.equal(logicV2.address);
});
it("should not upgrade without enough confirmations", async function () {
const proxyAsLogicV1 = await ethers.getContractFactory("MultiSigUpgrade").then(f => f.attach(proxy.address));
await proxyAsLogicV1.connect(owner1).submitUpgrade(logicV2.address, 2);
await proxyAsLogicV1.connect(owner2).confirmUpgrade(0);
const proxyAsLogicV2 = await ethers.getContractFactory("MultiSigLogicV2").then(f => f.attach(proxy.address));
await proxyAsLogicV2.connect(owner1).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.connect(owner1).submitUpgrade(logicV2.address, 2);
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, 2)).to.be.revertedWith("Not owner");
});
});
owners,需2人确认。value为42。LogicV2,setValue(10)返回20,version更新。versionHistory和Upgraded事件记录历史。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 ProxyAdminFactory = await ethers.getContractFactory("ProxyAdmin", admin);
const proxyAdmin = await ProxyAdminFactory.deploy();
await proxyAdmin.deployed();
console.log(`ProxyAdmin deployed to: ${proxyAdmin.address}`);
const TransparentProxyFactory = await ethers.getContractFactory("TransparentProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
const transparentProxy = await TransparentProxyFactory.deploy(logicV1.address, proxyAdmin.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
versionHistory和Upgraded事件记录版本。ProxyAdmin和事件追踪。versionHistory和事件。跑代码,体验Solidity版本控制的硬核玩法吧!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!