Union EVM 合约架构:Solidity 版 IBC 的存储承诺、组件拆分与轻客户端扩展机制
Union 是一个去信任化的零知识跨链桥协议,其在 EVM 侧的落点是一套用 Solidity 实现的 IBC(Inter-Blockchain Communication)协议栈。本文基于仓库中的 ARCHITECTURE.md,系统讲解这套 Solidity IBC 的仓库结构、组件架构、ICS-24 存储与承诺(commitment)设计,以及轻客户端的接入方式,并结合 evm/contracts 下的实际源码印证关键实现,帮助你理解"如何在受以太坊合约大小与存储模型限制的 EVM 链上,承载一个完整的 IBC 主机"。
1. 定位与背景:一套遵循 ICS 规范的 Solidity IBC
ARCHITECTURE.md 开篇明确了两点:
- 这是遵循 ICS(Interchain Specifications)规范与 ibc-go 语义的 Solidity IBC 实现;
- 文档的重点是阐述整体架构,以及设计者在以太坊与 Solidity 的语言/平台限制(合约部署大小上限、存储读写成本、存储布局规则等)下所做的取舍。
这两条定位决定了后文的两个核心主题:组件如何拆分、状态如何可被证明。
2. 仓库结构与 ICS 编号的对应关系
文档给出了 IBC 合约仓库的目录骨架,其中 core 目录名基本与 ICS 编号一一对应:
contracts/
apps/
ucs/01-relay/ … ucs01
clients/
CometblsClientV2.sol … Light Client for CometBLS consensus
…
core/
02-client/ … ics-02
03-connection/ … ics-03
04-channel/ … ics-04
24-host/ … ics-24
25-handler/ … ics-25
proto/ … code generated by solidity-protobuf
对照当前仓库 evm/contracts 下的实际内容,该结构被完整保留:
| 目录 | 对应 ICS | 实际文件 |
|---|---|---|
| core/02-client/ | ICS-02 客户端语义 | IBCClient.sol、ILightClient.sol |
| core/03-connection/ | ICS-03 连接 | IBCConnection.sol |
| core/04-channel/ | ICS-04 信道 | IBCChannel.sol、IBCPacket.sol |
| core/24-host/ | ICS-24 主机要求 | IBCStore.sol、IBCCommitment.sol |
| core/25-handler/ | ICS-25 处理器 | IBCHandler.sol、IBCMsgs.sol |
| clients/ | 具体轻客户端实现 | CometblsClient.sol、LoopbackClient.sol、StateLensIcs23MptClient.sol 等 |
| apps/ | 上层应用(UCS 协议) | ucs/00-pingpong/PingPong.sol、ucs/03-zkgm/Zkgm.sol |
| lib/ | 验证与编码库 | ICS23.sol、MPTVerifier.sol、CometblsZKVerifier.sol |
从源码结构看,clients/ 目录中的每个文件(如 CometblsClient、LoopbackClient、三个 StateLens 客户端)都实现第 5 节的 ILightClient 接口,这正是"任何 IBC 主机都可以即插即用地扩展新轻客户端"这一设计在目录层面的体现。
3. 架构总览:组件关系与初始化时序
文档给出的组件视图如下:Voyager(中继/提交节点)位于 EVM 链之外,链上由 ICS-02 客户端、ICS-03 连接、ICS-04 信道与 ICS-25 处理器四个组件构成,处理器是唯一的对外入口。
---
title: Components
---
flowchart BT
voyager(Voyager)
subgraph EVM chain with BN254 precompile
client(ICS-002 client)
connection(ICS-003 connection)
channel(ICS-004 channel)
handler(ICS-025 handler)
end
handler -- CometBLS client --> client
handler --> connection
handler --> channel
voyager --> handler
两个值得注意的细节:
- BN254 预编译:EVM 链一侧标注了 "with BN254 precompile"。CometBLS 共识依赖 BLS12-381 签名聚合验证,仓库中对应的零知识验证器实现见 CometblsZKVerifier.sol;在具备 BN254 预编译的链上(如以太坊主网的预编译合约)可利用其加速配对运算。
- 治理驱动的升级:文档强调"所有组件升级都由 Union 链通过治理发起"。这一约束在源码中有明确落地:核心合约全部继承 OpenZeppelin 的可升级代理体系,且权限收敛在
restricted修饰符之后。例如 IBCHandler.sol 声明为Initializable, UUPSUpgradeable, IBCStore, ...,其initialize(address authority)将权限授予 authority,_authorizeUpgrade被标记为restricted(仅 authority 可调用);而 authority 的角色管理由 Manager.sol 这类基于AccessManagerUpgradeable的合约承担,即由 Union 链上的 Access Manager 合约统一管控哪些地址可以执行哪些受限函数。
链上客户端的初始化时序(Setup Sequence)如下:先注册客户端类型、再创建客户端实例,随后依次建立 connection 与 channel,全部经由 Handler 转发:
---
title: Setup Sequence
---
sequenceDiagram
Voyager->>Handler: Register CometBLS client type
Handler->>ICS-002 Client: Register CometBLS client type
Voyager->>Handler: Create CometBLS client instance
Handler->>ICS-002 Client: Create CometBLS client instance
Voyager->>Handler: Create connection
Handler->>ICS-003 Connection: Create connection
Voyager->>Handler: Create channel
Handler->>ICS-004 Channel: Create channel
这条时序链可以在 IBCClient.sol 中找到对应实现:registerClient(string clientType, ILightClient client) 把"类型名 → 客户端实现地址"写入注册表并触发 RegisterClient 事件;createClient(IBCMsgs.MsgCreateClient) 则查注册表、生成客户端编号、调用具体客户端实现的 createClient,并把返回的承诺值写入 commitments 映射(详见第 4 节)。
4. 组件拆分:为什么分合同,以及 IBCStore 的统一存储
文档指出:为了缓解以太坊的合约大小上限,每个 ICS 实现被拆分为 IBCClient、IBCConnection、IBCChannel、IBCPacket 与 IBCHandler 五组合约。
文档同时给出了拆分设计的一般性代价与解法:
这类设计通常会导致存储被割裂(storage splitting),因此跨合约调用需要额外的鉴权与访问器实现。在 ibc-solidity 中,每个合约都继承定义了公共存储布局的 IBCStore 合约,并在合约调用中采用
delegatecall,以此规避该问题。
对照当前仓库源码,IBCStore.sol 确实定义了整个协议栈共享的"公共存储布局":
abstract contract IBCStore is AccessManagedUpgradeable {
// keccak256(IBC-compatible-store-path) => keccak256(IBC-compatible-commitment)
mapping(bytes32 => bytes32) public commitments;
mapping(string => address) public clientRegistry; // ClientType -> Address
mapping(uint32 => string) public clientTypes; // ClientId -> ClientType
mapping(uint32 => address) public clientImpls; // ClientId -> Address
mapping(uint32 => IBCConnection) public connections;
mapping(uint32 => IBCChannel) public channels;
mapping(uint32 => address) public channelOwner;
...
}
从源码结构看,当前的组织方式是:IBCClient、IBCConnectionImpl、IBCChannelImpl、IBCPacketImpl 均为抽象契约,最终由具体契约 IBCHandler 一次性继承组合。由于所有实现都根植于同一个 IBCStore 存储布局,跨 ICS 组件调用时读写的是同一份存储,天然避免了"存储割裂后需要额外鉴权与访问器"的问题——这正是文档所述设计目标(避免 storage splitting)在代码层的体现。仓库中显式的 delegatecall 用法则体现在应用层的代理转发模式上(如 Zkgm.sol 通过 delegatecall 转发到实现),与可升级代理的部署机制相配合。
IBCStore 还承担了几项跨组件的公共逻辑,例如 getClient(按 clientId 取轻客户端实例)、claimChannel / authenticateChannelOwner(信道归属的认领与鉴权)、ensureConnectionState / ensureChannelState(状态必须为 Open 的断言),这些都是 ICS-04/05 语义在主机侧的公共支撑。
5. Store 与 Commitment:让合约存储可被外部证明
这是本文档最核心的技术段落。IBC 的 ICS-24 定义了两种存储:provableStore 与 privateStore,原文档引用了规范要求:
The
provableStore:
- MUST write to a key/value store whose data can be externally proved with a vector commitment as defined in ICS 23.
- MUST use canonical data structure encodings provided in these specifications as proto3 files
The
privateStore:
- MAY support external proofs, but is not required to — the IBC handler will never write data to it which needs to be proved.
- MAY use canonical proto3 data structures, but is not required to.
Solidity IBC 的对应做法是:
- 状态本身:用 proto3 定义的 Solidity 类型(如
IBCConnection、IBCChannel)直接作为状态变量保存各 ICS 状态; - 承诺映射:额外维护一个映射状态变量满足
provableStore的"外部可证明"属性——键为 ICS-23Path的 keccak256 哈希,值为 ICS-23Value的 keccak256 哈希。
这与 IBCStore.sol 中 commitments 映射的注释逐字对应:keccak256(IBC-compatible-store-path) => keccak256(IBC-compatible-commitment)。
5.1 承诺路径与密钥生成器
哪些路径会被承诺?IBCCommitment.sol 定义了完整的 ICS-24 前缀常量与路径构造函数:
| 常量 | 值 | 含义 |
|---|---|---|
CLIENT_STATE |
0x00 | 客户端状态 |
CONSENSUS_STATE |
0x01 | 共识状态 |
CONNECTIONS |
0x02 | 连接 |
CHANNELS |
0x03 | 信道 |
PACKETS |
0x04 | 发送包批次 |
PACKET_ACKS |
0x05 | 回执批次 |
MEMBERSHIP_PROOF |
0x06 | 成员证明 |
NON_MEMBERSHIP_PROOF |
0x07 | 非成员证明 |
PACKET_TIMEOUTS |
0x08 | 超时批次 |
并配有"密钥生成器"系列函数,例如 clientStateCommitmentKey:
function clientStateCommitmentKey(uint32 clientId)
internal pure returns (bytes32)
{
return keccak256(clientStatePath(clientId));
}
即每条承诺的写入键都是 keccak256(path),其中 path 由前缀字节与标识(如 clientId、height)编码而成——这正是文档所述"以 ICS-23 Path 的 keccak256 为键"的具体实现。此外,NON_MEMBERSHIP_COMMITMENT_VALUE 定义为全零 32 字节后最后一位为 1(0x...001),用于在"写入非成员承诺"时占位。
5.2 存储槽位计算与 EVM 存储证明
文档回答了关键问题:如何为 commitments 这个映射状态获取证明? 依据 Solidity 的存储布局规范,若 commitments 映射位于槽位 s、ICS-23 承诺路径为 p,则对应的存储槽位为:
keccak256( keccak256(p) . s )
(即"映射键的哈希"与"映射所在槽位"拼接后再取 keccak256,这是 Solidity 对 mapping 类型键的标准布局方式。)
正因为槽位可由路径纯函数地算出,EVM 执行客户端提供的 eth_getProof(EIP-1186)就能直接查询任意一条承诺的存在性/不存在性证明——对端链(如 Union 链上的 StateLens 类轻客户端)验证 EVM 侧 IBC 状态时,只需验证 MPT 存储证明 + keccak256 承诺值,而无需读取 EVM 上的 Solidity 状态。仓库中与 EVM 状态证明对接的验证组件即 MPTVerifier.sol(MPT 存储证明验证)与 ICS23.sol(ICS-23 向量承诺编解码)。
一个细节可以印证承诺写入是"伴随状态写入"发生的:IBCHandler 的初始化 直接把三个序列计数器(nextClientSequence / nextConnectionSequence / nextChannelSequence)也写进了 commitments,因为它们同样属于需要可证明的主机状态;IBCClient.generateClientIdentifier 每次分配新的 clientId 时也是先读旧承诺、再写新承诺。
6. 轻客户端接入:ILightClient 接口与"状态自持"优化
文档明确:任何轻客户端都可以通过实现 ILightClient 接口 的合约接入,并通过 IBCHandler 上的 registerClient 函数注册。文档列出的四个核心函数及其语义为:
createClient:用给定状态创建新客户端;成功时返回初始状态的承诺;updateClient:更新指定 clientId 的客户端;成功时返回更新后状态的承诺;若共识状态无更新,返回空数组的 ConsensusStateUpdate 列表;verifyMembership:通用的成员证明验证,在指定高度验证某 CommitmentPath 上值的存在;调用方需依据 ICS-24 从 CommitmentPrefix 与标准路径构造完整的 CommitmentPath;verifyNonMembership:通用的非成员证明验证,在指定高度验证某 CommitmentPath 的缺失,路径构造要求同上。
对照 ILightClient.sol,当前接口还包含若干辅助成员,实际接入时需要一并实现:getTimestampAtHeight、getLatestHeight、misbehaviour(提交作恶证据,客户端应冻结自身)、getClientState、getConsensusState、isFrozen。返回值结构为:
struct ConsensusStateUpdate {
bytes32 clientStateCommitment;
bytes32 consensusStateCommitment;
uint64 height;
}
此外仓库中还存在 IForceLightClient 扩展接口(forceUpdateClient),对应 IBCClient.forceUpdateClient 这一受限的强制更新入口,属于治理层面的应急通道。
文档最后点出一个与 ibc-go 的关键设计差异:
与 ibc-go 不同,ibc-solidity 中的轻客户端合约把状态保存在自己的合约存储中。这是基于 Solidity 中序列化状态与读写存储高成本所做的优化。正因如此,
createClient与updateClient只返回承诺值(而非把状态写回主机存储)。
也就是说:客户端状态的"真身"在轻客户端合约自己的存储里,主机只保存其承诺摘要。这一架构使主机的存储开销收敛为常数级映射条目,同时也解释了为何 IBCHandler.createClient/updateClient 的职责被简化为"调用客户端实现 → 落承诺 → 发事件"(见 IBCClient.sol 第 59–104 行)。
7. 在仓库中继续深入:测试、部署与升级
围绕本文主题,以下仓库路径提供了可直接验证的纵深材料:
- 接口与状态机的单元测试:evm/tests/src/02-client/IBCClient.t.sol、evm/tests/src/02-client/CometblsClient.t.sol、evm/tests/src/03-connection/IBCConnection.t.sol、evm/tests/src/04-channel/IBCChannel.t.sol、evm/tests/src/04-channel/IBCPacket.t.sol,以及 evm/tests/src/lib/ICS23.t.sol、evm/tests/src/lib/MPTVerifier.t.sol 对 ICS-23 编码与 MPT 证明验证的独立测试;
- 各轻客户端实现:evm/contracts/clients/ 下的 CometblsClient(CometBLS 共识,约 670 行)、LoopbackClient(回环,用于 EVM 链间自测)与三个 StateLens 客户端(对接 Union 侧的 ICS-23/MPT/SMT 状态证明),它们都是第 6 节接口的具体实现;
- 部署与确定性地址:evm/README.md 说明所有部署合约都是可升级代理,通过 CREATE3 风格的 Deployer 使最终地址与创建代码无关,完全由
(deployer 源码, nonce, deployer 地址, sender, salt)决定,例如 salt"ibc-is-based"对应 IBCHandler;并给出了cast compute-address与nix run .#evm-contracts-addresses预计算部署地址、nix run .#eth-upgrade-*执行升级、eth-verify-*重验合约的完整命令行流程。这与第 3 节"组件升级由 Union 链治理发起"的约束共同构成生产环境的升级路径; - 部署脚本:evm/scripts/Deploy.s.sol 与 evm/scripts/Deployer.sol。
8. 小结
这套 Solidity IBC 架构回答了两个 EVM 特有的问题:其一,在合约大小上限约束下,按 ICS 编号拆分实现、并以 IBCStore 的统一存储布局保证跨组件状态一致性;其二,在 EVM 没有原生"向量承诺存储"的前提下,用"proto3 状态 + keccak256(path) → keccak256(value) 承诺映射"复刻 ICS-24 的 provableStore 语义,并借助 Solidity 确定性存储布局让 eth_getProof 成为跨链证明的入口。轻客户端则通过 ILightClient 接口与"状态自持、只落承诺"的优化解耦,使 CometBLS、Loopback、StateLens 等各类客户端都能在同一主机上注册并互通。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00