CEA和UEA 合约迁移流程

pushchain 发布于 2026-03-16 阅读 89

本文详细介绍了UEA和CEA的代理迁移流程,两者均采用delegatecall代理模式,通过自定义存储槽存储逻辑实现地址,迁移仅更新该指针,而所有代理状态(余额、nonce、映射等)完全保留。

概述

UEA(通用执行账户)和 CEA(链执行账户)均采用基于 delegatecall 的代理迁移模式。每个代理将其逻辑实现地址存储在一个专用的自定义存储槽中,而不是标准的 EIP-1967 槽。迁移仅更新此实现指针;代理的所有状态(余额、nonce、映射、身份)均完全保留。工厂控制哪个迁移合约处于活跃状态;用户无法提供任意的 delegatecall 目标。


共享概念

常量
UEA_LOGIC_SLOT 0x868a771a75a4aa6c2be13e9a9617cb8ea240ed84a3a90c8469537393ec3e115d
CEA_LOGIC_SLOT 0x8b2ae8ee8c8678fc65d38e03fd33865426627999aa5e8fab985583dec5888813
MIGRATION_SELECTOR bytes4(keccak256("UEA_MIGRATION")) — 对 UEA 和 CEA 使用相同的选择器

两个系统共有的关键属性:

  • 迁移合约受 onlyDelegateCall 保护——它们使用不可变的自身地址检查来防止直接调用。
  • 工厂(UEAFactory 或 CEAFactory)控制当前哪个迁移合约处于活跃状态。用户或 Vault 无法提供任意的目标地址。
  • 迁移不会自动应用。每个代理只有在显式触发时才会迁移。未触发的代理将继续运行旧逻辑,直到迁移被触发。

UEA 迁移流程

部署阶段(管理员)

1. 部署新的 UEA_EVM v2 和/或 UEA_SVM v2 实现合约
2. 部署新的 UEAMigration(UEA_EVM_v2_addr, UEA_SVM_v2_addr)
3. UEAFactory.setUEAMigrationContract(newMigrationAddr)
      └─ 更新工厂中的 UEA_MIGRATION_CONTRACT 指针

执行阶段(每个用户)

4. 用户签署 UniversalPayload {
       data:  MIGRATION_SELECTOR,
       to:    address(UEA),
       value: 0
   }
   — 或 — UE Module 代表用户提交迁移负载

5. 在 Push Chain 上调用 UEA.executeUniversalTx(payload, sig)

6. _handleMigration():
       验证 payload.to == address(this)
       验证 payload.value == 0

7. 读取 UEAFactory.UEA_MIGRATION_CONTRACT() → migrationAddr

8. UEAProxy 使用 "migrateUEAEVM()" 或 "migrateUEASVM()" 对 migrationAddr 进行 delegatecall
       (根据 UEA 变体选择对应的选择器)

9. UEAMigration 在代理存储上下文中运行:
       将新的实现地址写入 UEA_LOGIC_SLOT

10. 此后对 UEAProxy 的所有调用都将委托给新的实现

代理存储示意图

UEAProxy 存储(迁移前)                   UEAProxy 存储(迁移后)
──────────────────────────────────        ─────────────────────────────────
UEA_LOGIC_SLOT → UEA_EVM_v1              UEA_LOGIC_SLOT → UEA_EVM_v2
universalAccountId → {...}     (不变)    universalAccountId → {...}
ueaFactory → 0x...             (不变)    ueaFactory → 0x...
nonce → N                      (不变)    nonce → N

Delegatecall 链

                  Push Chain
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  UEAProxy                                               │
│  ┌─────────────────┐   delegatecall (fallback)          │
│  │ UEA_LOGIC_SLOT  │──────────────────────────────────► │
│  │  → UEA_EVM_v1   │                  UEA_EVM_v1        │
│  └─────────────────┘   executeUniversalTx()             │
│                        _handleMigration()               │
│                              │                          │
│                              │ delegatecall             │
│                              ▼                          │
│                        UEAMigration                     │
│                        migrateUEAEVM()                  │
│                              │                          │
│                              │ 写入 UEAProxy 存储       │
│                              ▼                          │
│  ┌─────────────────┐                                    │
│  │ UEA_LOGIC_SLOT  │ ◄── 更新为 UEA_EVM_v2              │
│  │  → UEA_EVM_v2   │                                    │
│  └─────────────────┘                                    │
└─────────────────────────────────────────────────────────┘

CEA 迁移流程

部署阶段(管理员)

1. 部署新的 CEA v2 实现合约
2. 部署新的 CEAMigration(CEA_v2_addr)
3. CEAFactory.setCEAMigrationContract(newMigrationAddr)
      └─ 更新工厂中的 CEA_MIGRATION_CONTRACT 指针

执行阶段(每个 CEA,由 Vault 驱动)

4. Push Chain 上的 UEA 所有者向 CEA 发送包含 MIGRATION_SELECTOR 负载的出站交易
       UniversalPayload { data: MIGRATION_SELECTOR, to: address(CEA), value: 0 }

5. TSS 观察到出站事件并将其中继到外部链

6. Vault.finalizeUniversalTx(subTxId, universalTxId, pushAccount, recipient,
                              token=address(0), amount=0, data=MIGRATION_SELECTOR_payload)

7. Vault → CEA.executeUniversalTx(subTxId, universalTxId, pushAccount, recipient, data)

8. CEA._handleMigration():
       验证 payload.to == address(this)
       验证 payload.value == 0

9. 读取 CEAFactory.CEA_MIGRATION_CONTRACT() → migrationAddr

10. CEAProxy 使用 "migrateCEA()" 对 migrationAddr 进行 delegatecall

11. CEAMigration 在代理存储上下文中运行:
        将新的实现地址写入 CEA_LOGIC_SLOT

12. 此后对 CEAProxy 的所有调用都将委托给新的实现

代理存储示意图

CEAProxy 存储(迁移前)                   CEAProxy 存储(迁移后)
──────────────────────────────────        ─────────────────────────────────
CEA_LOGIC_SLOT → CEA_v1                  CEA_LOGIC_SLOT → CEA_v2
pushAccount → 0x...            (不变)    pushAccount → 0x...
vault → 0x...                  (不变)    vault → 0x...
universalGateway → 0x...       (不变)    universalGateway → 0x...
isExecuted[subTxId] → {...}    (不变)    isExecuted[subTxId] → {...}

Delegatecall 链

                  外部链(例如以太坊)
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  CEAProxy                                               │
│  ┌─────────────────┐   delegatecall (fallback)          │
│  │ CEA_LOGIC_SLOT  │──────────────────────────────────► │
│  │  → CEA_v1       │                  CEA_v1            │
│  └─────────────────┘   executeUniversalTx()             │
│                        _handleMigration()               │
│                              │                          │
│                              │ delegatecall             │
│                              ▼                          │
│                        CEAMigration                     │
│                        migrateCEA()                     │
│                              │                          │
│                              │ 写入 CEAProxy 存储       │
│                              ▼                          │
│  ┌─────────────────┐                                    │
│  │ CEA_LOGIC_SLOT  │ ◄── 更新为 CEA_v2                  │
│  │  → CEA_v2       │                                    │
│  └─────────────────┘                                    │
└─────────────────────────────────────────────────────────┘

安全属性

  • onlyDelegateCall 防护:迁移合约在构造时检查其自身地址是否与调用上下文的地址不同。直接调用会回滚;只有在 delegatecall 上下文中(此时 address(this) 指代理合约)才能成功执行。
  • 工厂控制的目标:迁移合约地址在执行时从工厂读取。用户(UEA)或 Vault(CEA)都无法提供任意的 delegatecall 目标,从而防止恶意逻辑注入。
  • 状态保留:迁移仅更新实现槽。代理存储中的所有余额、nonce、身份字段和执行记录均不受影响。
  • 无强制迁移:未触发的代理将无限期地停留在当前实现上。每个代理必须显式触发迁移;不存在批量或自动升级路径。
  • 原文链接: github.com/pushchain/pus...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论