如何编写稳健且可持续的智能合约

s1lv3r

版主

s1lv3r Rep
0
0
0
Rep
0
s1lv3r Vouches
Vouches
0
8 MONTHS
8 8 MONTHS OF SERVICE
s1lv3r 获得
0
0
0
获得
0
s1lv3r 凭证
0
0
0
凭证
0
主题
22
点赞
0
HakB
8
在社区成长 8 月
LEVEL 90 100 XP
 
好的智能合约既稳健又可持续。稳健性描述了智能合约的容错水平,包括在不影响用户的情况下处理严重故障的能力。强大的智能合约系统还能够快速检测有问题的操作并有效响应以避免问题。
可持续性描述了智能合约的可维护性和灵活性水平。可持续合同可以进行调整和延期,而不会产生大量开销或引入漏洞。它还应该将策略(高级组件)与实现(低级组件)分开,并允许安全有效地更换实现。

特权管理控制和合约所有权​

构建稳健且可持续的合同首先要实施安全访问控制。通过访问控制,您可以决定谁可以触发智能合约中的特定操作以及在什么情况下触发。这有利于智能合约系统的稳健性和可持续性:
稳健性: 具有管理权限的帐户可以执行安全操作(例如冻结传输)以响应漏洞或错误
Sustainability: 管理员帐户可以执行日常维护操作,例如调整协议参数或将合同升级到新的实施
然而,确保访问控制机制可靠且安全非常重要, 未经授权的帐户永远不应访问合约的敏感功能。举例来说,如果对手获得对代币铸造功能的未经授权的访问,那么不良的访问控制可能会导致项目遭受无限的铸造黑客攻击。
Ownable 模式在控制对智能合约中重要功能的访问方面很流行。实现此模式的项目有一个特殊帐户,该帐户“拥有”特定合约并被允许执行管理操作。受保护的函数通常具有onlyOwner 修饰符,并且只能由设置为所有者的地址调用- 从其他地址对该函数的调用只需还原。
虽然合约所有权提高了执行管理操作的速度和效率,但它可能会增加集中度并在协议中引入单点故障。更理想的方法是将管理权限分配给多个地址。在这里,您可以定义具有特权角色的帐户白名单(可以更新),以确保执行维护操作的权力不会集中在单个实体中。
您仍然可以选择拥有具有广泛管理权限的单个帐户(相对于使用基于角色的访问控制),但它应该绑定到多重签名钱包地址。像Safe 这样的多重签名钱包在执行交易之前需要获得预定数量的参与者的批准,从而提供一些保护,防止管理员作弊或受到损害。

可暂停性​

虽然您可以通过限制对特权帐户的访问来保护管理功能,但保护对与协议交互的所有用户开放的功能免遭滥用则更加困难。一种常见的解决方法是编写逻辑,以便在发现严重错误时“暂停”智能合约的功能。
在这种情况下,调用暂停函数的事务将恢复,而涉及其他函数的用户操作正常进行。此功能可以帮助避免在紧急情况下降低用户体验- 例如,当您的团队致力于修补漏洞时,您可能希望允许从借贷协议中提取资产,但不允许存款和借款。
可暂停性特别理想,因为它允许协议开发人员减轻安全问题对整个系统的影响。如果无法暂停功能,另一种选择可能是销毁受影响的合约或冻结整个协议。
然而,我们应该注意到,暂停合同是一种缓解措施;一种故障安全机制,可以提高协议从对抗性攻击中恢复并保护用户的能力。暂停功能并不是针对漏洞的第一道防线(正如我们的一位审计员所说的那样:“如果攻击者首先发现错误,那么你就会遇到问题”)。
您可以从头开始为合约编写暂停功能,也可以使用现有的库(例如OpenZeppelin 的Pauseable.sol)向函数添加暂停修饰符。下面是Steven Traykovski 在智能合约中的暂停功能的简单实现。它使用全局暂停布尔变量与require 语句配对作为函数执行的前提条件:
//全局布尔变量用于控制称为“paused”的暂停功能
bool 公共暂停;
//然后在函数中使用“require”语句实现该变量,以便在函数执行之前其值必须解析为“false”
函数transfer1(地址,uint256金额)外部{
require(paused==false, '函数暂停');
require(balances[msg.sender]=amount, '代币不足');
余额[msg.sender] -=金额;
余额[至] +=金额;
}
//必须添加另一个函数来更改布尔变量“paused”的值。 “require”语句用于限制对合约所有者的访问并防止其他用户调用该函数
函数setPaused(bool _paused) 公共{
require(msg.sender==Owner, '你不是所有者');
暂停=_暂停;
}

使用减速带和速率限制​

减速带是对智能合约的整体安全至关重要的功能执行的编程延迟。例如,项目可能会延迟用户提款或使用时间锁来延迟智能合约系统的升级。在这两种情况下,减速带模式可确保除非经过预定义的时间窗口,否则无法执行某些操作。
减速带是必要的,因为有时,处理恶意行为的最可行的选择是在修复漏洞或实施缓解措施时减慢攻击者的速度。也就是说,减速带代表了速度、用户体验和安全性之间的权衡,因此在使用它们之前您需要考虑项目的需求。
减速带还可以通过保护用户免受强行对他们进行不需要的更改的企图来帮助去中心化。如果协议的治理制定了新的升级,则反对该更改的用户有时间在升级生效之前从系统中退出资金。这通常是通过使用时间锁来实现的,该时间锁会延迟治理提案的执行,直到达到某个阈值(例如提案获得批准后48-72 小时)。
速率限制类似于减速带,通常在金融智能合约中实施以提高安全性。速率限制可以采取多种形式,例如限制用户在单笔交易中可以提取或交易的金额,或者限制智能合约在一个时间窗口内可以铸造的代币数量。
与减速带一样,速率限制可能会影响用户体验,例如,用户可能需要将大型交易拆分为多个较小的交易。但这可能是一个很好的权衡,因为它可以减少攻击者在发现漏洞时可能造成的损害。
警告: 减速带和速率限制可能会给人一种错误的安全感,而且并不总是正确实施。这两种机制都比可拥有/可暂停模式更复杂,因此这是可以预料的。与所有智能合约安全功能(包括本文中描述的功能)一样,应将减速或速率限制作为提高协议抵御对抗性攻击的弹性的综合计划的一部分来实施。

使用代理模式升级合约​

区块链上运行的合约代码是不可变的,排除了在部署后添加新功能或修复错误的可能性。不过,可以在不更改用户交互的代码的情况下模拟逻辑升级——这是使用“代理模式”来实现的。
代理模式使用两个合约,一个是存储状态(数据)的“代理合约”,另一个是存储业务逻辑的“实现合约”。用户与代理合约交互,但实现合约负责执行函数调用。
这是可能的,因为代理合约持有一个引用实现合约的地址变量,并使用delegatecall 将所有函数调用委托给它。由于使用了delegatecall,函数在代理合约的上下文中执行,而实现合约则写入前者的存储中。
通过分离合约的数据和业务逻辑,您可以“升级”合约,而无需部署新的合约实例并从旧合约迁移数据。您只需要部署一个新的实现合约,并通过更改存储的地址变量将代理指向它。
透明代理模式和通用可升级代理标准(UUPS)是使用可升级代理合约的两种常见方法。对于透明代理,代理合约存储实现合约的地址、将代理升级到新实现的函数以及可以调用升级函数的地址(通常标记为Owner)。
代理模式使得在不破坏现有交互的情况下升级合约成为可能——用户与同一个代理进行交互,并且不会注意到实现合约中的更改。部署新的实施合约也比将状态复制到新的合约实例更具成本效益。
但您应该意识到,代理模式很复杂,如果实施不正确,可能会带来一些问题。这包括函数选择器冲突和存储冲突等问题。

监控智能合约并实施自动响应​

开发阶段的严格测试可能并不总能防止部署到主网后智能合约中出现错误的可能性。这就是为什么监控合约交易很重要——注意到可疑操作可以让您有足够的时间来响应可能的(或正在进行的)攻击。
合约监控的理想方法是使用事件跟踪交易。发出的事件应包含有关操作的详细信息,包括作为参数传递的数据,以便您深入了解发生的情况。
要监视的操作示例包括:
涉及管理行为的操作(例如铸造新代币);
交易量异常增加;
存款/取款激增;
可疑交易等
虽然您可以手动侦听事件(使用Webhooks 等),但自动化智能合约监控会更有效。 Hal Notify 等自动监控工具提供了一个实时通知系统,可以跟踪链上活动,让您随时了解合约状态。
将交易监控与OpenZeppelin Defender 等自动化智能合约安全工具结合起来也是一个好主意。借助Defender,项目团队可以监控合约交易,并在检测到可疑模式时自动触发保护措施,例如暂停合约。

使智能合约可组合​

可组合性是重用或组合现有的软件组件来构建新的应用程序。在web3 开发的背景下,可组合性允许开发人员通过集成存储在其他地方的代码(例如,通过合约继承)来扩展或修改智能合约的功能。
与其他面向对象的编程语言一样,Solidity 允许合约(“子”类)从另一个合约(“父”类)继承函数。一个智能合约可以继承多个合约(多重继承),包括抽象合约、接口合约和库。
抽象契约和接口是特殊类型的契约,因为它们只声明函数而不实现它们。继承抽象合约或接口的合约使用自定义逻辑来实现基类中定义的函数。
从这个意义上说,合约继承通过将函数的声明与其实现解耦,使智能合约系统更加灵活和可持续。如果需要,您可以通过更改合约实现接口或抽象合约定义的函数的方式来修改或扩展合约的逻辑。
契约继承还通过减少必须编写的代码量来增强开发的简单性和安全性。如果您与经过实战考验的合约库集成,并且受益于开源社区的广泛审核和测试,则尤其如此。
 
顶部