利用Crytic提升可升级合约安全性
可升级合约并不像你想象的那么安全。可升级架构可能存在缺陷,导致合约锁定、数据丢失或破坏事件恢复能力。每次合约升级都必须仔细审查以避免灾难性错误。最常见的delegatecall代理模式存在我们之前记录过的缺陷。
Crytic现在包含一套完整的17项可升级性检查,帮助您避免这些陷阱。
操作指南
审查可升级合约是一项复杂的底层任务,需要调查存储布局和内存中的函数组织。我们创建了支持可升级性的示例代币(crytic/upgradeability-demo)来演示具体步骤。这个简单演示库包含:
- MyToken:简单代币的初始实现
- Proxy:我们的代理合约
对Proxy的任何调用都会使用delegatecall在MyToken上执行其逻辑,而存储变量将保存在Proxy中。这是大多数可升级合约的标准设置。
假设这两个合约已部署在主网上,但MyToken代码已过时,需要更改功能。现在是时候使用MyTokenV2了!MyTokenV2代码与MyToken类似,只是移除了init()函数及其相关状态变量。
让我们使用Crytic来确保部署MyTokenV2不会引入新的安全风险。
配置
首先,在Crytic设置中配置可升级合约:
您可以配置:
- 被升级的合约
- 使用的代理
- 合约的新版本
注意:(1)和(2)是可选的;Crytic将运行所有适用的检查。
例如,如果您只有可升级合约而没有代理或新版本,Crytic已经可以查找初始化模式中的缺陷。如果您有可升级合约和代理但没有新版本,Crytic可以查找实现和代理之间的函数冲突。如果有多个可升级合约或多个代理,可以配置任何适合您设置的组合。
配置完成后,可升级性检查将在每次提交和拉取请求时运行,类似于安全检查和单元测试。
Crytic的发现结果
有时Crytic会在您的可升级性代码中发现严重错误(哦不!)。我们在演示中构建了一个这样的问题。当Crytic发现安全问题时,显示如下:
was_init存储变量被移除,导致balances在MyToken和MyTokenV2中具有不同的存储偏移量,破坏了合约的存储布局。
这是一个常见错误,在具有许多合约和继承关系的复杂代码库中特别难以手动发现——但Crytic会为您捕捉这个问题!
Crytic还能发现什么?
Crytic将根据您的配置审查:
- 升级和代理之间的存储布局一致性
- 代理和实现之间的函数冲突
- 正确的初始化模式
- 变量使用的最佳实践
详细检查列表:
编号 | 检测内容 | 影响程度 | 需要代理 | 需要新版本 |
---|---|---|---|---|
1 | 不应为常量的变量 | 高 | X | |
2 | 函数ID冲突 | 高 | X | |
3 | 函数遮蔽 | 高 | X | |
4 | 缺少init函数调用 | 高 | ||
5 | initializer()未被调用 | 高 | ||
6 | 多次调用初始化函数 | 高 | ||
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