---
# System prepended metadata

title: 代理合约的演进
tags: [以太坊基础]

---

## 无代理合约
由于区块链的不可篡改特性，一个合约上链之后，其代码将被永久性保存，并且不可更改。在这种情况下，如果项目方部署的合约存在漏洞，而资金又存储在合约中，有可能导致资金被盗取。

## UUPS(ERC1822 通用可升级代理)
ERC1822提出了通用可升级代理模式，该模式定义了代理合约的基础架构：
![image](https://hackmd.io/_uploads/ByWXmCQjJe.png)

即将合约分为逻辑合约和代理合约，逻辑合约包含要执行的逻辑（不存储数据，只存有逻辑），而代理合约充当入口，用于调用逻辑合约（存储数据）。
此时，交易会直接于代理合约接触，代理合约通过委托调用（借用逻辑合约的代码）执行交易并更改状态（状态保存在代理合约中）。

但是当前的代理合约依然存在一些问题：

1. 选择器冲突：用户与代理合约交互（通过函数调用），由于代理合约中不存储逻辑，因此会触发fallback函数（在fallback内部委托调用逻辑合约）。然而，当代理合约与逻辑合约中存在两个函数选择器相同的函数时，将导致这次函数调用作用于代理合约中。这导致执行错误逻辑。
2. 存储冲突：由于存在委托调用，因此执行的上下文还是在代理合约中。如果逻辑合约的状态变量布局与代理合约不同，将导致存储时冲突。举例来说：
代理合约的状态存储布局：


        | state_name | slot     |
        |:---------- | -------- |
        | a          | 0x00...0 |
        | b          | 0x00...1 |

        逻辑合约的状态存储布局：

        | state_name | slot     |
        | ---------- | -------- |
        | c          | 0x00...0 |
        | a          | 0x00...1 |
        | b          | 0x00...2 |
    可以发现：如果在逻辑合约中修改状态变量a，那么将修改slot为0x00...1的值，但是在代理合约中0x00...1对应的状态变量是b。这就导致错误的状态修改，甚至有可能导致逻辑合约地址被篡改。


3. 用户角色不明确：在当前UUPS模型中，任何用户都可以修改逻辑合约的地址，这意味着任何人都可能直接修改逻辑合约的实现。

## ERC1967
针对上述ERC1822遗留的问题，ERC1967解决了一部分。首先，ERC1967定义了几个专用的存储槽：
  ○ bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)：逻辑合约地址槽。
  ○ bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)：管理员地址槽。
  ○ bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)：信标合约地址槽。
这些存储槽被预先定义，从而防止存储冲突修改这些关键变量的值，因此解决了上述的问题2。

## TransaprentProxy
透明代理的提出解决了上述的问题1和问题3。
透明代理主要思想为：
1. 权限分离：将升级合约的权限交给管理员，将执行业务逻辑的权限交给普通用户。
2. 逻辑分离：将升级合约的逻辑存储在代理合约中，将业务逻辑存储在逻辑合约中。

因此，在透明代理中有两种角色：
1. 普通用户：只能够调用逻辑合约中的内容。
2. 管理员：只能够调用升级的逻辑，并且升级的逻辑存储在代理合约中。

透明代理的代码模式如下：
1. 代理合约：包含逻辑转发函数、升级函数以及一些查询函数。
2. 逻辑合约：包含业务逻辑。

针对问题1选择器冲突：在透明代理中，规定代理合约中只有逻辑转发函数、升级函数等，因此不存在问题1。

针对问题3角色不明确：透明代理对用户代理进行了明确的分层，因此解决了问题3。

## BeaconProxy

信标代理用于解决多个代理合约共用逻辑合约时，修改逻辑合约地址的效率低下问题，举例来说：公司中某个部门有 100 名员工，你想给每个人都换一部 iPhone16ProMax。
如果使用传统代理，你只能一个个换。但如果使用 BeaconProxy，信标代理，那么你可以一次性将这个部门的所有员工的手机都更换。

信标代理相当于一个扩展功能，集中管理多个代理合约的逻辑合约。
![image](https://hackmd.io/_uploads/S1VUVR7oyg.png)

交易先发给代理合约，而后代理合约在信标代理中查询实现的地址，而后在根据返回的实现地址执行交易。
![image](https://hackmd.io/_uploads/Hy0UVCmike.png)


可以发现，信标代理就是一个实现地址的存储器，所有实现信标代理的合约都需要先从信标合约中拿到实现地址，再执行交易。这也就是为什么信标代理可以一次性更新所有代理的实现地址。
