Source code
查看 ArtStarERC1967Proxy 合约源码
1. 文档概述
1. 文档概述
真正持有链上状态的不是
RWAToken 或 ComplianceManage 实现合约,而是这个代理实例。这份文档的重点是解释为什么一个只有构造函数的合约依然是系统架构中不可忽视的一环。2. 合约职责与存在原因
2. 合约职责与存在原因
ArtStarERC1967Proxy 的职责可以概括为两点:- 作为标准化代理载体,持有可升级合约的状态。
- 为 Hardhat Ignition 部署流程提供一个稳定、明确、可复用的代理合约入口。
- 用户交互的地址通常是代理地址,而不是实现合约地址。
- 实现合约可以被替换,而代理地址和代理状态会被保留。
- 业务合约的初始化并不是在实现合约构造函数中完成,而是在部署代理时通过
data一次性调用。
3. ERC-1967 标准说明
3. ERC-1967 标准说明
ERC-1967 是代理模式中的一个关键标准,它规定了实现合约地址等关键元数据应该存在哪些固定存储槽位中。标准化这些槽位的价值在于:- 避免代理内部状态与业务存储槽冲突。
- 让链上工具、区块浏览器和审计流程更容易识别代理关系。
- 让升级和部署遵循业界通用约定,而不是项目自定义布局。
ERC1967Proxy 实现,这些标准化槽位由 OpenZeppelin 负责维护。4. UUPS 代理模式说明
4. UUPS 代理模式说明
本项目采用的是 UUPS 升级模式,而不是透明代理模式。这意味着
- 透明代理 (Transparent)
- UUPS
通常把升级管理能力放在代理侧。
ArtStarERC1967Proxy 自己并不包含升级授权策略。真正决定“谁可以升级”的是实现合约中的 _authorizeUpgrade():RWAToken通过onlyOwner控制升级授权。ComplianceManage也通过onlyOwner控制升级授权。
理解 UUPS 的关键:
- 保存状态的是代理。
- 决定升级的是当前实现合约中的授权逻辑。
5. 当前代理的最小封装设计
5. 当前代理的最小封装设计
当前源码如下:这是一种典型的最小封装设计,完全复用 OpenZeppelin 代理行为。
ArtStarERC1967Proxy.sol
好处:
- 代理层代码面积极小,审计面更可控。
- 升级语义尽量贴近成熟库默认行为。
- 部署脚本可以显式引用项目自己的代理合约名称,方便生成稳定 artifact 和部署记录。
代价:
- 如果不了解
ERC-1967与 UUPS,容易误以为该合约“没什么重要性”。 - 升级风险更多转移到实现合约和运维流程,而不是通过代理层做保护。
6. 初始化数据与部署流程
6. 初始化数据与部署流程
该代理构造函数接收两个参数:
implementation:初始实现合约地址。data:部署代理后立即委托执行的初始化 calldata。
7. 与 Ignition 部署模块的关系
7. 与 Ignition 部署模块的关系
Ignition 模块对该代理的使用非常直接:
ComplianceManage:先部署实现合约,再把initialize(deployer)编码后作为data传入代理,映射为ComplianceManageProxy。RWAToken:先部署实现合约,再把名称、符号等参数编码为initialize(...)传入代理,映射为RWATokenProxy。
8. 与实现合约的职责边界
8. 与实现合约的职责边界
代理合约与实现合约的职责应严格区分:
- 代理合约:负责保存状态、接收外部调用并把调用委托给实现合约。
- 实现合约:负责定义业务逻辑、权限控制、升级授权和存储布局。
9. 升级风险与运维注意事项
9. 升级风险与运维注意事项
主要风险体现在以下几个方面:
1
存储布局风险
实现合约新增、删除或重排状态变量时,可能破坏代理中已有状态。
2
初始化风险
如果未来版本引入新的初始化路径,必须谨慎使用 reinitializer,避免重复初始化或遗漏初始化。
3
地址识别风险
部署、验证和前后端接入时,必须使用代理地址作为系统入口,而不是误用实现合约地址。
4
权限认知风险
团队若误以为代理合约持有升级治理逻辑,可能漏看实现合约中的
_authorizeUpgrade()。10. 为什么该合约代码极短但文档仍然必要
10. 为什么该合约代码极短但文档仍然必要
它连接了三个高风险主题:
- 代理存储与业务存储如何共存。
- 实现合约如何在不改地址的情况下升级。
- 部署脚本如何一次性完成代理初始化并让业务系统真正可用。