Solidity里的代理合约(ProxyContract),这可是区块链开发里一个超级实用的技术,能让你的智能合约像手机系统一样支持“升级”,同时保持数据和地址不变。区块链的合约一旦部署,默认是不可变的,但用代理合约,你可以把逻辑和数据分开,随时替换逻辑合约,简直是开发者的救星!不过,代理合约也有
Solidity里的代理合约(Proxy Contract),这可是区块链开发里一个超级实用的技术,能让你的智能合约像手机系统一样支持“升级”,同时保持数据和地址不变。区块链的合约一旦部署,默认是不可变的,但用代理合约,你可以把逻辑和数据分开,随时替换逻辑合约,简直是开发者的救星!不过,代理合约也有坑,比如存储冲突、权限管理、调用安全等。
先来搞清楚几个关键点:
delegatecall调用逻辑合约,代码在逻辑合约执行,但上下文(存储、msg.sender等)属于代理合约。咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从简单代理到复杂UUPS实现,逐步打造安全的代理合约。
用Hardhat搭建开发环境,写和测试合约。
mkdir proxy-demo
cd proxy-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
目录结构:
proxy-demo/
├── contracts/
│ ├── SimpleProxy.sol
│ ├── LogicV1.sol
│ ├── LogicV2.sol
│ ├── TransparentProxy.sol
│ ├── UUPSProxy.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── Proxy.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: Double 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:设置owner,防止重复初始化。setValue:设置value,仅owner可调用。getValue:返回value。setValue将输入值翻倍。implementation(逻辑合约地址),owner。upgrade:更新implementation,仅owner可调用。fallback:通过delegatecall转发调用到implementation,使用汇编确保低级调用。receive:接收ETH。implementation的代码,但使用代理合约的存储。owner和value,逻辑合约只提供逻辑。onlyOwner限制升级。implementation不为空。test/Proxy.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();
// Initialize through proxy
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 through proxy", 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); // V2 doubles the value
expect(await proxyAsLogicV2.owner()).to.equal(owner.address); // Storage preserved
});
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保持不变。addr1无法升级。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/Proxy.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); // Storage messed up
});
LogicV1的存储是owner(slot 0), value(slot 1),BadLogicV2是value(slot 0), owner(slot 1),升级后owner被覆盖为value的值。storage关键字显式声明槽位。用OpenZeppelin的透明代理,解决管理调用冲突。
contracts/LogicV1.sol(更新,使用OpenZeppelin升级库):
// 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/Proxy.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/Proxy.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。addr1无法升级。scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [owner, admin] = 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 ProxyFactory = await ethers.getContractFactory("TransparentProxy");
const initData = LogicV1Factory.interface.encodeFunctionData("initialize", [owner.address]);
const proxy = await ProxyFactory.deploy(logicV1.address, admin.address, initData);
await proxy.deployed();
console.log(`TransparentProxy deployed to: ${proxy.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}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
npx hardhat run scripts/deploy.ts --network hardhat
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!