区块链网站|NFTS 区块链 打破一成不变的区块链 代理模式如何以最佳方式实现智能合约的升级?

打破一成不变的区块链 代理模式如何以最佳方式实现智能合约的升级?

广告位

打破区块链的不可篡改,代理模式如何以最佳方式实现智能合约升级?

声明:本文旨在传递更多市场信息,不构成任何投资建议。文章仅代表作者观点,不代表MarsBit官方立场。

边肖:记得要集中注意力。

资料来源:CertiK

原标题:打破一成不变的区块链,代理商模式如何以最佳方式实现智能合约的升级?

代理模式使智能合约能够升级其逻辑,同时维护其在链上的地址和状态值。对代理契约的调用将通过delegateCall执行逻辑契约中的代码来修改代理契约的状态。本文将概述代理合同的类型、相关的安全事件和建议,以及使用代理合同的最佳实践。

可升级契约与代理模型介绍

我们都知道区块链的“不可更改”特性也说明了智能合约代码在区块链上部署后是不可修改的。

因此,当开发人员希望更新合同代码以进行逻辑升级、错误修复或安全更新时,他们必须部署新的合同,并且将生成新的合同地址。

要解决这个问题,可以使用代理模式。

代理模式实现了契约的升级,不改变契约的部署地址,是目前最常见的契约升级模式。

代理模式是一个可升级的契约体系,包括代理契约和逻辑实现契约。

代理处理用户交互以及数据和合同状态存储。用户对代理契约的调用将通过delegatecall执行逻辑契约中的代码,从而改变代理契约的状态。通过更新记录在代理契约的预定存储槽中的逻辑契约地址来实现升级。

有三种传统的代理模式:透明代理、UUPS代理和信标代理。

透明代理在透明代理模式下,升级功能在代理契约中实现。代理的管理员角色被给予操作代理契约的直接许可,以更新代理的相应逻辑实现地址。没有管理员权限的调用方会将其调用委托给实现协定。

注意:代理契约管理员不能是逻辑实现契约的关键角色,甚至不能是普通用户,因为代理管理员不能与实现契约进行交互。

UUPS代理在UUPS(通用可升级代理标准)模式下,契约升级功能在逻辑契约中实现。因为升级机制存储在逻辑契约中,所以升级后的版本可以删除与升级相关的逻辑,以禁止将来的升级。在这种模式下,对代理契约的所有调用都将被转发到逻辑实现契约。

信标代理信标代理模式允许多个代理契约通过引用信标契约来共享相同的逻辑实现。信标契约为被调用的代理契约提供逻辑实现契约的地址。当升级到一个新的逻辑实现地址时,只需要更新信标契约中记录的地址。

误用和安全事故

开发者可以使用代理模式契约来实现可扩展的契约系统。但是,代理模式也有一定的操作门槛,如果使用不当,可能会给项目带来毁灭性的安全问题。以下部分显示了与代理滥用和代理带来的集中化风险相关的事件。

代理的密钥泄露:代理管理员控制透明代理模式的升级机制。如果管理员的私钥泄露,攻击者可以升级逻辑契约,在代理状态下执行自己的恶意逻辑。

2021年3月5日,付费网络遭遇私钥管理不当导致的“造币”攻击。付费网络被攻击者利用,攻击者窃取代理管理员的私钥,触发升级机制改变逻辑契约。升级后,攻击者可以破坏用户的付费,为自己铸造一批付费,然后出售。代码本身不存在安全缺陷,只是攻击者从管理员那里获得了升级合同的私钥。

未初始化的UUPS代理实现对于UUPS代理模式,在代理契约初始化过程中,初始参数从调用者传递给代理契约,然后代理契约调用逻辑契约中的initialize函数实现初始化。

initialize函数通常由\” initializer \”修饰符保护,以限制函数只被调用一次。调用initialize函数后,从代理契约的角度初始化逻辑契约。但是,从逻辑契约的角度来看,逻辑契约是不初始化的,因为initialize不是在逻辑契约中直接调用的。由于逻辑契约本身尚未初始化,任何人都可以调用initialize函数来初始化它,将状态变量设置为恶意值,并可能接管逻辑契约。

逻辑契约接管的影响取决于系统中的契约代码。最坏的情况下,攻击者可以将UUPS代理模式下的逻辑契约升级为恶意契约,并执行“自毁”函数调用,这可能导致整个代理契约变得无用,契约中的资产将永久丢失。

情况

奇偶多sig冻结:逻辑契约未初始化。攻击者通过调用自毁,触发了多个钱包的初始化,并在合同中锁定了泰币。

Harvest Finance、Teller、KeeperDAO和Rivermen都使用未初始化的逻辑契约,这将允许攻击者任意设置契约的初始化参数,并在委托调用过程中执行自毁来破坏代理契约。

存储冲突

在可升级的契约系统中,代理契约不声明状态变量,而是使用伪随机存储槽来存储重要数据。

代理将逻辑协定状态变量的值保存在声明它们的相对位置。如果代理契约声明它自己的状态变量,并且代理和逻辑契约都试图使用同一个存储槽,则会发生存储冲突。OpenZeppelin库提供的代理契约并不在契约中声明状态变量,而是基于EIP 1967标准,将需要存储的值(如管理地址)保存到特定的存储槽中,以防止冲突。情况

2022年7月23日,北京,去中心化音乐平台Audius被黑。这次事件是因为代理合同引入新逻辑导致的存储冲突。

代理声明了一个proxyAdmin地址状态变量,当执行逻辑契约代码时,它的值将被错误地读取。

项目方私自定义的proxyAdmin的值被错误地当作initialized和initializing的值,使得初始化器修改器返回错误的结果,从而让攻击者再次调用初始化器函数,授予自己管理契约的权限。攻击者随后更改了投票参数,并通过了他们窃取奥迪斯资产的恶意提案。

在逻辑协定或不受信任的协定中调用delegatecall假设delegatecall存在于逻辑协定中,但该协定没有正确验证调用目标。在这种情况下,攻击者可以利用这个函数来执行对其控制下的恶意契约的调用,从而破坏逻辑实现或者执行自定义逻辑。

同样,如果逻辑契约中存在不受限制的address.call函数,一旦攻击者恶意提供地址和数据字段,就可以作为代理契约使用。

情况

Pickfinance,Furucombo和dYdX攻击。

在这些事件中,易受攻击的合约已经被用户令牌批准,并且在用户提供的合约中存在调用/delegatecall来调用合约地址和数据。攻击者将能够调用带有transferFrom函数的契约来提取用户的余额。在dYdX事件中,dYdX为了保护资金进行了自己的白帽攻击。

最优方法

一般来说,只有在必要的时候才使用代理模式。

不是每个合同都需要升级。如上所示,使用代理模式存在许多风险。“可升级”属性也可能导致信任问题,因为代理管理员可以不经社区同意就升级合同。我们建议仅在必要时将代理模式集成到项目中。

不要修改代理库。

代理库非常复杂,尤其是处理存储管理和升级机制的部分。修改中的任何错误都将影响代理和逻辑合同的工作。审计过程中发现的大量与代理相关的高严重性错误都是由于代理库修改不正确造成的。奥迪斯事件就是一个典型的例子,它显示了不当修改代理合同的后果。

代理运营管理要点初始化逻辑契约

攻击者可以接管未初始化的逻辑契约,并可能破坏代理契约系统。因此,请在部署后初始化逻辑协定,或者在逻辑协定的构造函数中使用_ disableInitializers来自动禁用初始化。

确保代理管理账户的安全。

可升级的合同系统通常需要“代理管理员”的特权角色来管理合同的升级。如果管理密钥泄露,攻击者可以自由地将合同升级为恶意合同,窃取用户的资产。我们建议小心管理代理管理帐户的私钥,以避免任何被黑客攻击的潜在风险。多签名钱包可用于防止单点密钥管理失败。

使用独立账户进行透明的代理管理。

管理和逻辑治理应该是独立的地址,以防止失去与逻辑实现的交互。如果代理管理和逻辑治理引用同一个地址,则不会转发任何调用来执行特权功能,从而禁止改变治理功能。

代理存储相关在代理契约中声明状态变量时要小心。

正如奥迪斯黑客事件中所解释的,代理契约在声明自己的状态变量时必须谨慎。在代理契约中以正常方式声明的状态变量,在读写数据时会造成数据冲突。如果代理契约需要状态变量,请将该值保存在类似于EIP1967的存储槽中,以防止在执行逻辑契约代码时发生冲突。

维护变量声明顺序和逻辑契约类型。

逻辑契约的每个版本都必须保持相同的状态变量顺序和类型,并且需要在现有变量的末尾添加一个新的状态变量。否则,委托调用将导致代理契约读取或覆盖不正确的存储值,旧数据可能与新声明的变量相关联,这将给应用程序带来严重的问题。

在基础合同中包含存储缺口。

该逻辑需要在契约代码中包含存储间隙,以便在部署新的逻辑实现时可以预测新的状态变量。添加新的状态变量后,间隙大小需要适当更新。

不要在构造函数或声明过程中设置状态变量的值。

在声明期间或在构造函数中分配状态变量只会影响逻辑契约中的值,而不会影响代理契约。不可变参数应该使用initialize函数进行赋值。

合同继承)可升级合同只能继承其他可升级合同。

与不可升级的合同相比,可升级的合同具有不同的结构。例如,构造函数与更改代理状态不兼容,它使用initialize函数来设置状态变量。任何继承另一个契约的契约都需要使用其继承的契约的initialize函数来分配自己的变量。当使用OpenZeppelin库或编写自己的代码时,请确保可升级协定只能继承其他可升级协定。

不要在逻辑契约中实例化新契约。

通过Solidity创建的实例化合同将不可升级。契约应该单独部署,它们的地址应该作为参数传递给可伸缩的逻辑契约,以达到可伸缩的状态。

母合同初始化风险

当初始化父合同时,函数__{ContractName}_init将初始化其父合同。调用多个__{ContractName}_init可能会导致父协定的第二次初始化。注意_ _ {ContractName} _ init _ unchained只会初始化{ contract name }的参数,不会调用其父契约的初始化器。

但是,这不是推荐的做法,因为所有的父协定都需要初始化,并且未能初始化所需的协定将导致将来的实现问题。

逻辑契约的实现避免对不可信的契约使用自毁或委托调用/调用。

如果协定中存在自毁或委托调用,攻击者就有可能使用这些函数来破坏逻辑实现或执行自定义逻辑。开发人员应该验证用户的输入,并且不允许契约执行对不受信任的契约的delegatecall/calls。另外,不建议在逻辑契约中使用delegatecall,因为管理多个契约的代理链中的存储布局会比较麻烦。

写在最后

通过允许协议在部署后更新其代码逻辑,代理绕过了区块链的不可变特性。但是,代理合同的制定仍然需要非常谨慎,不正确的执行可能会导致项目安全和逻辑问题。

一般来说,最佳实践是使用权威的、经过广泛测试的解决方案,因为透明的、UUPS和信标代理模型对于它们各自的用例都有经过验证的升级机制。此外,还应该安全地管理升级代理的特权角色,以防止攻击者改变代理逻辑。

逻辑契约的实现也应该注意不要使用delegatecall,这可以防止攻击者执行一些恶意代码,比如自毁。

虽然遵循最佳实践可以确保代理合同部署的稳定性,同时保持升级的灵活性,但所有代码都容易出现新的安全或逻辑问题,从而可能危及项目。因此,最好由一组在审计和保护代理合同方面有经验的安全专家(如CertiK)对所有代码进行适当的审计。

广告位
本文来自网络,不代表区块链网站|NFTS立场,转载请注明出处:https://www.qklwz.com/qkl/14614.html
上一篇
下一篇

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

返回顶部