以太坊的诞生建立了一种基于不变性原理的新计算模型,源代码一旦部署到区块链上,就永远存在,无法更改。然而,在软件开发的现实中,这种不变性给错误修复、功能更新和系统优化带来了主要障碍。为了解决区块链的不变性和软件灵活性需求之间的冲突,代理合约模型被开发为标准解决方案。这份由 Tan Phat Digital 团队编写的报告深入分析了代理合约的本质、底层技术机制、流行标准,尤其是在现实场景中容易让最终用户受骗的复杂安全风险。
1.核心概念以及状态和逻辑的分离
以太坊上的传统智能合约是一个整体实体,其中执行代码(逻辑)和存储空间(存储)绑定到单个地址。代理合约打破了这种结构,将系统分为两个独立但紧密交互的组件:代理合约(保存状态)和逻辑合约(保存可执行代码)。
该模型允许为用户和前端应用程序维护固定地址,而底层业务逻辑可以通过更改指向代理内存内逻辑合约的地址来替换。这在本质上不可变的基础设施上造成了“可变性的幻觉”。
1.1 代理架构中组件的角色
在此架构中,代理合约充当单点联系。所有资产、以太币余额以及重要的状态变量(例如所有权和用户余额)都永久存储在代理的地址中。相比之下,逻辑合约(也称为实现合约)仅包含如何处理数据的指令。
主要组件包括:
代理合约:数据存储、代币余额、实现地址和管理权限。具有不可变的地址属性,但可以更改内部数据。
逻辑(实现):包含业务功能的源代码(例如:转账、铸币、交换)。可被新版本替换。
ProxyAdmin:管理合约或钱包有权执行升级,有助于控制更改整个系统的能力。
这种分离给系统维护带来了巨大的好处。如果业务逻辑中发现安全漏洞,开发者只需部署新的固定逻辑合约并更新代理中的地址,而不必要求数千名用户将资产转移到全新的地址。
另请参阅:什么是智能合约?
2. DELEGATECALL机制:升级的引擎
代理合约运行的技术基础是以太坊网络EIP-7中引入的操作码delegatecall。这是一个特殊的调用,允许合约从另一个地址获取可执行代码,但在自己的“上下文”中运行。
2.1 在 EVM 执行中保留上下文
当代理合约对逻辑合约进行delegatecall时,交易的环境变量将保持不变。这意味着在逻辑合约中,与直接调用代理相比,以下值不会改变:
msg.sender:仍然是原始用户的地址,而不是代理的地址。msg.value:保留交易中包含的以太币数量资源。存储:所有读取 (SLOAD) 和写入 (SSTORE) 都直接作用于代理的存储空间,而不是逻辑合约。
常规调用 (CALL) 和 DELEGATECALL 之间的区别对于安全性和功能性极其重要。系统能力。在 CALL 命令中,目标合约在自己的内存上运行,并将 msg.sender 视为调用合约。在DELEGATECALL中,目标合约充当加载到代理服务器中的逻辑库。
2.2 通过 Fallback 函数分析执行流程
由于 Proxy Contract 通常不会定义业务函数以避免冲突,因此它使用 fallback 函数来接收任何未知的调用。当用户在Proxy上调用transfer(address,uint256)函数时,由于Proxy没有此函数,以太坊虚拟机(EVM)会触发fallback函数。这里会执行一段低级汇编代码来授权调用。
技术流程如下:
使用
calldatacopy将交易的整个输入数据复制到内存中。从指定的存储位置获取当前逻辑合约的地址。
执行命令
delegatecall使用所有可用的gas,传入复制的数据。逻辑合约完成执行后,结果返回,并使用
returndatacopy将输出数据复制回来。代理将结果返回给原始用户,透明地完成委托周期。
3.内存架构和 EIP-1967 标准
代理模式的最大挑战之一是管理内存冲突(存储冲突)。由于Proxy和Implementation共享相同的Proxy存储空间,如果它们都尝试使用相同的内存“槽”用于不同的目的,就会发生灾难。
3.1内存冲突风险
在Solidity中,状态变量按照声明的顺序排列在32字节的槽中。假设Proxy在槽0中声明变量address_implementation来存储逻辑地址。如果逻辑合约也在槽0中声明了变量uint256 _balance,那么每次逻辑函数更新余额时,它都会意外地覆盖自己在代理中的地址。如果攻击者可以操纵写入该槽的值,这会导致合约被攻击者接管。
3.2 非结构化存储和 EIP-1967 解决方案
为了防止冲突,建立了 EIP-1967 标准,该标准通过散列标识符字符串定义代理管理变量的特殊存储位置:

散列后减1可确保该槽无法通过Solidity中的动态数组或映射的正常散列创建,从而将碰撞概率突然降至几乎零。
查看更多:什么是智能合约审计?
4.流行的代理模型及权衡分析
4.1 透明代理模式(TPP)
该模型旨在解决“函数标识符冲突”问题。 TPP 的机制基于调用者去中心化:如果调用者是 Admin,则 Proxy 只调用自己的管理功能;如果调用者不是管理员,则代理始终将调用转发给实现。
4.2 通用可升级代理标准 (UUPS) - EIP-1822
UUPS 通过将升级逻辑从代理移动到实现合约本身,从而优于 TPP。代理现在非常精简,有助于节省燃料,因为无需在代理上检查管理员。但是,如果升级到缺乏升级逻辑的新版本,合约将被永久锁定。
4.3 Beacon Proxy
专为需要部署一系列相同合约的项目而设计。代理指向一个名为“Beacon”的中间合约,该合约保存通用的实现地址。当 Beacon 更新时,所有依赖的代理将同时升级。
4.4 钻石标准(EIP-2535)
钻石标准允许一个代理合约连接到许多不同的实施合约(Facet)。这通过拆分逻辑并允许每个部分独立升级来解决 24KB 合约大小限制。
5.安全风险与用户欺骗机制
根据 Tan Phat Digital 的观察,当用户在区块浏览器上看到未更改的合约地址或经过验证的源代码时,常常会被错误的安全感所欺骗。
5.1 管理和中心化的风险
最大的风险是管理员的绝对控制。如果管理密钥被泄露,攻击者可以对恶意源代码进行“Flash升级”以提取资金,然后升级回旧版本以消除痕迹。
5.2初始化错误(未初始化代理漏洞)
由于Proxy无法使用构造函数,因此开发者使用initialize函数。如果管理员忘记调用该函数或者没有保护它,攻击者可以自己调用它来接管管理员权限。 2017 年 Parity 钱包黑客攻击冻结了超过 500,000 ETH,这是该错误的一个痛苦的历史例子。
5.3 内存冲突和 Audius 崩溃
2022 年 Audius 协议黑客攻击说明了内存插槽错误:
升级之前:插槽 0 存储创建的初始状态(
初始化= true)。合约正常工作。升级后:开发者将变量
proxyAdmin添加到Proxy中,不小心与slot 0重叠。利用:Admin的地址值导致系统误认为尚未初始化。黑客再次调用
initialize函数并获取所有权。损害:黑客从社区基金中转移 600 万美元代币。
5.4 隐藏和伪装技术
欺诈者经常将危险函数命名为 safeWithdraw(),但实际上它是一个函数从而耗尽资金。他们还只能验证代理的源代码(非常短且无害),同时隐藏包含恶意“后门”的实现源代码。
6。典型Rug Pull代理案例分析(2024-2025)
6.1 LIBRA案例和Rug Pull演变
2025年LIBRA项目使用“碎片Rug Pull”策略来逃避检测。欺诈者不是进行大笔提款,而是使用代理将提款分配到一系列卫星钱包,在监控系统警告阈值以下执行数千笔小额交易。然后,他们将代理升级为包含 selfdestruct 命令的合约,以消除证据。
6.2 Kinto Finance 和低级代理漏洞
2025 年 7 月,Kinto Finance 通过代理管理机制中的错误受到攻击。攻击者接管了升级权,并迅速额外铸造了11万个未经授权的代币,从流动性池中提取了155万美元。
7.用户测试和验证流程
为了保护资产,Tan Phat Digital 建议用户采取以下步骤:
检查验证代理功能:查看 Etherscan 是否显示“读取为代理”选项卡。
监控升级历史记录:如果合约升级过于频繁且没有通知,则属于危险信号。危险。
验证管理员权限:管理员地址应是多重签名钱包(Multisig)或具有 Timelock 机制以延迟重要更改。
使用支持工具:利用 GoPlus Security 或 TokenSniffer 等平台扫描 Honeypot 或异常 Mint 代币权限的迹象。
8.开发人员防御策略
开发人员应使用 OpenZeppelin 标准库,并始终应用“存储间隙”技术(创建空数组 uint256 private __gap;)来节省内存空间。实施中的任何更改都必须在部署之前通过 Hardhat Upgrades Plugin 等自动化工具测试内存布局兼容性。
9.常见问题 (FAQ)
为什么使用代理合约而不是部署新合约?使用代理有助于为用户和集成应用程序维护单一、固定的合约地址,避免昂贵的数据迁移(迁移)并要求用户在每次有更新或错误修复时移动到新地址。
CALL 和 DELEGATECALL 命令之间的核心区别是什么?在
CALL命令中,目标合约在自己的内存上运行。相比之下,DELEGATECALL加载目标合约的代码,但在调用合约的“上下文”中执行,这意味着它使用内存、余额并保留调用合约的msg.sender。为什么透明代理比 UUPS 消耗更多的 Gas? 透明代理需要在每笔交易中检查调用者是否是管理员,以决定调用的方向(向管理员还是向逻辑)。 UUPS 消除了代理处的此检查,因为升级逻辑直接位于实施合约中。
如何识别使用代理的项目?在 Etherscan 上,用户可以找到“这是代理吗?”按钮。或“作为代理读取”/“作为代理写入”选项卡。如果是的话,那肯定是一份授权合同。
什么是存储间隙?这是一种在基础合约中放置空数组(例如
uint256 private __gap;)的技术,为将来的新变量保留存储空间,防止遗留合约的内存位置冲突。不初始化Proxy有什么风险?如果部署后没有立即调用
initialize函数,攻击者可以自己调用该函数,成为合约的所有者并完全控制。为什么Proxy不能使用构造函数?因为
构造函数中的代码只在部署时在逻辑合约的地址运行一次,而实际的用户状态在Proxy的地址。什么是函数选择器冲突? 这是两个函数具有不同名称但具有相同 4 字节标识符的现象。黑客可以利用这一点诱骗用户调用业务功能,但实际上激活了危险的管理功能。
如何保证Proxy升级安全?项目应采用多重签名钱包(Multisig)结合Timelock机制(延迟执行时间),以便社区在新源码生效前有时间进行测试。
可以禁用代理升级吗? 可以。开发人员可以升级到不包含升级逻辑的实现,或者使用管理豁免使代理成为永久不可变的合约。
信标代理与常规代理有何不同?每个代理不是存储逻辑地址,而是指向中间合约(信标)。更新信标将在一次事务中同时升级数千个依赖代理。
ProxyAdmin 合约的作用是什么?在 OpenZeppelin 的透明代理模型中,
ProxyAdmin是一个中间合约,它拥有代理的所有权,有助于安全地管理升级,并将其与常规用户交互分开。如何检测代理中的后门?用户应检查实现源代码是否经过验证,并使用Slither等工具扫描具有
onlyOwner权限的可以提取资金或阻止交易的函数。什么是Flash升级攻击?这是一种黑客将Proxy升级为恶意代码,盗取资金,然后立即通过一笔交易升级回旧版本,从而消除链上痕迹的攻击技术。
如何以最安全的方式初始化Proxy?开发者应该使用原子初始化流程,即部署Proxy和调用
initialize函数发生在同一个交易中,以避免被黑客“盯上”。
代理合约是区块链生态发展的必然技术方案。然而,如果管理不透明,这种灵活性也会带来风险。 Tan Phat Digital 认为,治理的透明度和用户的审慎性是社区在动荡的 Web3 世界中需要追求的真正“不变性”。
分享








