使用Crytic让可升级合约更安全
可升级合约并不像你想象的那么安全。可升级性架构可能存在缺陷,导致合约被锁定、数据丢失或破坏你从事件中恢复的能力。每个合约升级都必须仔细审查,以避免灾难性错误。最常见的delegatecall代理存在我们之前记录过的缺点。
Crytic现在包含了一套全面的17项可升级性检查,帮助你避免这些陷阱。
操作指南
审查可升级合约是一项复杂的底层任务,需要调查存储布局和内存中函数的组织。我们创建了一个支持可升级性的示例代币来帮助逐步了解crytic/upgradeability-demo中的步骤。这个简单的演示库包括:
- MyToken:我们简单代币的初始实现
- Proxy:我们的代理
对Proxy的任何调用都将在MyToken上使用delegatecall来执行其逻辑,而存储变量将保存在Proxy上。这是大多数可升级合约的标准设置。
假设这两个合约已经部署在主网上。然而,MyToken的代码已经过时,你需要更改其功能。是时候使用MyTokenV2了!MyTokenV2的代码与MyToken类似,只是移除了init()函数及其相关的状态变量。
让我们使用Crytic来确保部署MyTokenV2不会引入新的安全风险。
配置
首先,告诉Crytic关于你的可升级合约的信息。转到你的Crytic设置并找到这个面板:
在这里你可以配置:
- 正在升级的合约
- 使用的代理
- 合约的新版本
注意:(1)和(2)是可选的;Crytic将运行尽可能多的适用检查。
例如,如果你只有可升级合约,没有代理或新版本,Crytic已经可以查找初始化模式中的缺陷。如果你有可升级合约和代理,但没有新版本,Crytic可以查找实现和代理之间的函数冲突。如果你有多个可升级合约或多个代理,你可以配置任何适合你设置的组合。
回到MyToken,我们有这三个合约:
一旦我们配置了Crytic,可升级性检查将在每次提交和拉取请求时运行,类似于安全性检查和单元测试:
Crytic的发现
有时,Crytic会在你的可升级性代码中发现严重错误(哦不!)。我们在演示中构建了这样一个问题。当Crytic发现安全问题时,情况如下:
was_init存储变量被移除,因此balances在MyToken和MyTokenV2中具有不同的存储偏移量,破坏了合约的存储布局。
这是一个常见的错误,在具有许多合约和继承关系的复杂代码库中可能特别难以手动发现——但Crytic会为你发现问题!
Crytic还能发现什么?
Crytic将审查(取决于你的配置):
- 升级和代理之间的存储布局一致性
- 代理和实现之间的函数冲突
- 正确的初始化模式
- 变量使用的最佳实践
以下是详细的检查列表:
编号 | 检测内容 | 影响 | 需要代理 | 需要新版本 |
---|---|---|---|---|
1 | 不应为常量的变量 | 高 | X | |
2 | 函数ID冲突 | 高 | X | |
3 | 函数遮蔽 | 高 | X | |
4 | 缺少init函数调用 | 高 | ||
5 | initializer()未被调用 | 高 | ||
6 | Init函数被多次调用 | 高 | ||
7 | v2中变量顺序不正确 | 高 | X | X |
8 | 代理中变量顺序不正确 | 高 | X | X |
9 | 具有初始值的状态变量 | 高 | ||
10 | 应为常量的变量 | 高 | X | |
11 | 代理中的额外变量 | 中 | X | |
12 | v2中缺少变量 | 中 | X | |
13 | v2中的额外变量 | 信息 | X | |
14 | 未继承Initializable | 信息 | ||
15 | 缺少Initializable | 信息 | ||
16 | 必须调用的初始化函数 | 信息 | ||
17 | 缺少initializer() | 信息 |
使用Crytic检查你的合约
除了发现90多个漏洞外,Crytic现在还可以检测可升级性代码中的缺陷。它是唯一能够深入保护你的代码库免受如此多问题影响的平台。如果你想避免灾难性错误,请在部署任何可升级合约之前使用Crytic。
有问题吗?加入我们的Slack频道(#crytic)或在Twitter上关注@CryticCI。
如果你喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News