本次审计对 OffchainLabs/stylus-sdk-rs 仓库在特定 commit 进行了安全审计,发现了 36 个问题,包括严重、高、中、低风险问题。审计范围涵盖 stylus-proc, stylus-sdk, examples, mini-alloc 目录下的多个文件。审计结果表明该项目尚处于开发阶段,但团队积极解决问题,建议在问题解决和项目成熟后进行后续审计。
TypeSmart Contract LanguageTimelineFrom 2024-07-08To 2024-08-09LanguagesRustTotal Issues36 (27 resolved, 1 partially resolved)Critical Severity Issues2 (1 resolved, 1 partially resolved)High Severity Issues2 (2 resolved)Medium Severity Issues10 (7 resolved)Low Severity Issues12 (8 resolved)Notes & Additional Information10 (9 resolved)
我们审计了 commit 62bd831 上的 OffchainLabs/stylus-sdk-rs 仓库。
以下文件在审计范围内:
stylus-proc
└── src
├── calls
│ └── mod.rs
├── lib.rs
├── methods
│ ├── entrypoint.rs
│ ├── error.rs
│ ├── external.rs
│ └── mod.rs
├── storage
│ ├── mod.rs
│ └── proc.rs
└── types.rs
stylus-sdk
└── src
├── abi
│ ├── bytes.rs
│ ├── const_string.rs
│ ├── export
│ │ ├── internal.rs
│ │ └── mod.rs
│ ├── impls.rs
│ └── internal.rs
│ └── mod.rs
├── block.rs
├── call
│ ├── context.rs
│ ├── error.rs
│ ├── mod.rs
│ ├── raw.rs
│ ├── traits.rs
│ └── transfer.rs
├── contract.rs
├── crypto.rs
├── debug.rs
├── deploy
│ ├── mod.rs
│ └── raw.rs
├── evm.rs
├── hostio.rs
├── lib.rs
├── msg.rs
├── prelude.rs
├── storage
│ ├── array.rs
│ ├── bytes.rs
│ ├── map.rs
│ ├── mod.rs
│ ├── traits.rs
│ └── vec.rs
├── tx.rs
├── types.rs
└── util.rs
examples
├── erc20
│ └── src
│ ├── erc20.rs
│ ├── lib.rs
│ └── main.rs
├── erc721
│ └── src
│ ├── erc721.rs
│ ├── lib.rs
│ └── main.rs
└── single_call
└── src
├── lib.rs
└── main.rs
mini-alloc
├── src
│ ├── imp.rs
│ └── lib.rs
└── tests
└── misc.rs
Arbitrum Stylus 通过允许将程序编译为 WebAssembly (WASM) 并在链上部署,与用常见 EVM 语言(如 Solidity)编写的传统智能合约无缝共存,从而引入了智能合约开发中的范式转变。这种与语言无关的方法为开发人员开辟了新的可能性,使他们能够使用自己喜欢的编程语言,同时保持与 Ethereum 生态系统的完全 ABI 兼容性。
Stylus 程序最显著的方面之一是其卓越的性能和成本效益。这些程序的执行成本比传统的基于 EVM 的智能合约便宜和快速几个数量级,同时还与 EVM 完全兼容。这一突破使 Stylus 程序能够与现有的 Ethereum 智能合约无缝交互,从而在 WASM 的效率和已建立的 Ethereum 生态系统之间建立桥梁。
这个强大的工具包使开发人员能够用 Rust 为 Arbitrum 链编写程序。Rust 的性能、安全性和现代功能相结合,使其成为开发强大而高效的智能合约的理想选择。Rust 的 Stylus SDK 提供了一套全面的工具和抽象,简化了创建 Stylus 程序的过程。它为 Rust 程序员提供了熟悉的开发体验,同时与 Arbitrum 生态系统无缝集成。SDK 中可用的一些功能包括:
Stylus Rust SDK 利用几个强大的过程宏来简化智能合约开发并确保与 EVM 生态系统的无缝集成。这些宏自动执行复杂的任务,例如特征实现、方法公开、存储管理和合约间通信,从而使开发人员能够编写符合语言习惯的 Rust 代码,同时保持与使用 EVM 兼容语言制作的合约的完全兼容性。
entrypoint
此宏定义 Stylus 执行的入口点。它实现了 TopLevelStorage
特征,通常用于注释顶层存储结构体。此宏设置了处理传入调用、解析调用数据和序列化结果所需的样板代码。它还管理重入保护,与 Stylus VM 的执行模型集成。
external
此宏用于使方法“外部化”,以便可以被其他合约调用。它为带注释的 impl
块实现 Router
特征。该宏处理 ABI 编码和解码、方法选择器生成以及与 Stylus VM 调用约定的集成的复杂性。它还管理 purity 注释(pure
、view
、payable
),如果未明确指定,则可以根据方法签名推断这些注释。此宏可以具有 #[inherit]
属性来实现类似继承的行为,允许合约包含来自父合约的方法。
sol_interface
它将 Solidity 接口定义转换为 Rust 结构体和方法,从而实现与其他合约的无缝交互。该宏处理方法生成、类型转换、函数选择器计算和调用上下文管理。它支持各种调用类型(pure
、view
和 payable
),并适应重入问题。通过自动创建 ABI 兼容的 Rust 代码,它简化了跨合约通信,同时保持了类型安全和符合语言习惯的 Rust 实践。此宏充当翻译器,使 Rust 合约能够与更广泛的 EVM 生态系统集成。
solidity_storage
此属性宏应用于 Rust 结构体,以使其能够在智能合约内的持久存储中使用。结构体中的每个字段都必须实现 StorageType
特征,该特征确保 EVM 存储模型兼容性。应用此宏允许开发人员直接在 Rust 中定义存储布局,字段映射到 EVM 中的相应存储槽。此宏确保 Rust 结构体的存储布局与 Solidity 的存储布局对齐,从而促进与现有 Solidity 合约的无缝升级和交互。它支持嵌套结构体和各种存储类型,如 StorageAddress
、StorageBool
以及实现 StorageType
的自定义类型。
sol_storage
此宏允许使用类似 Solidity 的语法定义 Rust 结构体。它确保这些结构体的存储布局与其 Solidity 对应物相同。通过允许开发人员直接在 Rust 中重用其 Solidity 类型定义,从而简化了从 Solidity 到 Rust 的过渡,同时保持与现有存储布局的兼容性。此宏在底层使用 solidity_storage
宏。
derive_solidity_error
此宏允许将 Rust 枚举用于合约方法中的错误处理。它使枚举能够自动转换为 Solidity 兼容的错误消息,这些消息可以由智能合约函数返回。在底层,该宏通过为 Vec<u8>
实现 From<YOUR_ERROR>
以及打印 export-abi
的代码来工作。
derive_erase
此宏自动为结构体实现 Erase
特征。它生成一个 erase()
方法,该方法在结构体的每个字段上调用 erase()
。这允许轻松清除 Arbitrum Stylus 智能合约中的复杂存储结构,确保正确擦除所有字段,而无需手动实现。该宏无法为不支持它的类型(如映射)实现 Erase。
以下模块包含在 stylus-sdk
文件夹中,以促进智能合约开发以及与 Stylus WASM 模块的交互:
abi
abi
模块提供根据 Ethereum 应用程序二进制接口 (ABI) 规范编码和解码数据的功能。它支持 Solidity 和 Rust 类型之间的双向映射,从而允许 Rust 和 Solidity 合约之间的互操作性。该模块支持将函数调用编码为字节数组,并将合约响应解码回 Rust 类型。abi
模块还包括生成方法选择器和导出 Solidity 接口的实用程序,将 Vec<u8>
视为 Solidity 中的 uint8[]
,并使用 Bytes
类型表示 Solidity bytes
。此功能对于基于 Rust 的智能合约和用 Solidity 编写的智能合约之间的通信至关重要。
call
call
模块通过处理执行上下文并提供标准和原始合约调用的机制来管理与外部合约的交互。它允许开发人员指定 gas 限制和调用值,并访问合约存储。该模块包括缓存策略以优化重复状态访问,以及在重入调用期间安全执行的功能。通过管理这些方面,call
模块使基于 Rust 的合约能够与 Ethereum 合约交互,从而促进合约通信和数据交换。
deploy
deploy
模块有助于在 Arbitrum 网络上部署合约。它包括标准和原始部署的功能,从而提供对部署过程的灵活性和控制。raw.rs
文件提供了较低级别的部署函数,从而允许更精细的控制和潜在的不安全操作。该模块支持设置部署参数、处理部署过程并确保正确的合约初始化。
storage
storage
模块提供了一个全面的框架来管理智能合约存储,其中包含用于常见数据结构(如数组、字节、映射和向量)的抽象。它通过一组定义的特征支持基本和复杂的存储操作,从而确保正确的数据处理和访问。该模块允许开发人员通过实现 StorageType
特征来定义自定义存储逻辑,从而在基于 Rust 的合约中启用更高级的数据操作,同时提供持久存储访问。Stylus 合约在共享相同 EVM State Trie 的虚拟机上运行,从而允许访问 Ethereum 的键值存储。该模块提供类型和特征,用于使用 Rust 的借用检查器进行安全存储访问,从而防止不安全的存储别名。
hostio
hostio
模块有助于基于 Rust 的智能合约与 Arbitrum 区块链上的宿主环境之间的交互。它提供了一组函数,用于通过 Stylus WASM VM 的外部函数接口来管理合约状态、执行调用和处理 I/O 操作,该接口最终与核心区块链通信。wrap_hostio
宏是此模块的关键组件,旨在简化和简化定义宿主函数的过程。它将底层宿主操作包装在 Rust 安全抽象中,自动生成绑定以与区块链交互。Stylus SDK 中的几个单文件模块广泛使用 wrap_hostio
模块来提供典型的区块链交互,例如:
block
: 提供对 Ethereum 区块信息的访问,包括区块号、时间戳和矿工详细信息等属性。它充当检索有关区块链上当前或过去区块的数据的接口。contract
: 促进与其他合约的交互,从而启用函数调用和对合约元数据的访问。它允许合约执行诸如余额检查和合约代码检索之类的操作。crypto
: 提供加密函数和实用程序,例如哈希算法和签名验证。evm
: 与 EVM 交互,管理执行资源和日志记录。它包括查询剩余 gas 和 ink(特定于 Stylus 的计算单元)、以原始和 alloy 类型格式发出日志以及管理内存增长的实用程序。msg
: 处理消息传递操作,提供有关当前事务的信息,例如发送者、值和 gas。它允许合约与交易数据交互并控制流程。tx
: 提供与事务相关的功能,包括访问 gas 价格和来源等事务详细信息。它使合约能够处理特定于事务的数据并执行基于事务的逻辑。该分配器是 Stylus 生态系统的关键,针对 Arbitrum Stylus 等 wasm32 目标进行了优化。它使用最小的 bump 分配器策略,优先考虑简单性和效率。值得注意的是,mini-alloc 从不释放内存,这非常适合二进制大小约束严格且可以接受泄漏所有分配的场景。这种设计选择提高了性能,与 Stylus 专注于优化区块链环境(传统内存管理会增加不必要的开销)相一致。
Arbitrum Stylus SDK 仓库包含三种常见智能合约设计的示例 crate:erc20
、erc721
和 single_call
。
erc20
: 演示了 ERC-20 代币合约,具有诸如铸造、转移和检查余额之类的功能。它包括代币转账和管理津贴的方法。erc721
: 说明了 ERC-721 NFT 合约的实现,涵盖了唯一代币的铸造、转移和查询所有权。它还处理代币元数据并确保与 ERC-721 标准的兼容性。single_cal
: 展示了一个简单的合约,用于对另一个合约进行单次调用,演示了在 Arbitrum 环境中使用 Stylus SDK 的合约间通信。文档 断言,Stylus 中的结构体字段将映射到与 EVM 编程语言中相同的存储槽,并且布局将与 Solidity 的布局相同。这表明从 Solidity 升级到 Rust 不会导致存储槽错位,从而意味着可以轻松传输类型定义。
但是,Stylus 处理继承存储的方式与 Solidity 不同。例如,考虑以下 Solidity 代码:
contract Parent {
bool a = true;
bool b = true;
}
contract Child is Parent {
bool c = true;
bool d = true;
}
在这种情况下,存储槽 0 包含 0x01010101
。相反,等效的 Stylus 代码使用 borrow
子句,该子句使用新的存储槽并且不打包状态变量,从而导致不同的存储布局:
// parent.rs 片段
sol_storage! {
pub struct Parent {
bool a;
bool b;
}
}
// lib.rs 片段
sol_storage! {
#[entrypoint]
struct Child {
#[borrow]
Parent parent;
bool c;
bool d;
}
}
#[external]
#[inherit(Parent)]
impl Child {
// ...
}
给定相同的设定值,这将导致 0x0101
位于槽 0 中,而 0x0101
位于槽 1 中。
对于使用代理模式从 Solidity 迁移到 Stylus 的项目来说,这种差异至关重要,因为它可能导致存储布局错位,从而有可能覆盖状态变量。
考虑重构 solidity_storage
宏 以镜像 Solidity 的行为。或者,考虑更新文档以准确反映当前行为,并避免误导开发人员。
更新: 已在 commit a99d8c5 和 6e3a62e 中解决。已添加内联文档,以高亮显示与 Solidity 存储布局的差异。
external 宏的功能之一是自动暴露出 Stylus 智能合约中的 Rust 函数作为 Ethereum 兼容的方法。为此,它会分析一个实现块,处理每个方法以生成其选择器,然后使用这些选择器将传入的调用路由到相应的 Rust 函数。
但是,该宏不验证这些选择器的唯一性。这种疏忽可能导致选择器冲突,从而导致方法无法访问。此外,开发人员可以使用 #[selector(id = <NUMBER_THAT_GENERATES_THE_COLLISION>)]
手动为函数设置自定义选择器,从而可能有意或无意地复制现有选择器。此漏洞可用于创建恶意合约,例如蜜罐,其中方法被故意设置为无法访问。
考虑在此宏中实现验证机制,以确保所有选择器都是唯一的,从而防止选择器冲突并提高合约的可靠性和安全性。一种方法是在 route
函数上添加 #[deny(unreachable_patterns)]
语句,以防止无法访问的方法。
更新: 已在 commit a78decd 和 0d50c1d 中部分解决。使用 #[deny(unreachable_patterns)]
的建议解决方案是不够的,因为它只检查应用它的特定匹配语句中的无法访问的模式。它不考虑与继承合约中的函数的选择器冲突,从而使这些冲突无法检测到。Offchain Labs 已添加内联文档以高亮显示此潜在问题,并且警告也将包含在文档网站上。Offchain Labs 团队正在探索实现 Stylus 继承的替代方法。
sol_interface
宏sol_interface
宏允许开发人员使用其原生接口从 Stylus 智能合约无缝调用 Solidity 合约。但是,可以很容易地操纵此宏,以误导用户了解函数的实际状态可变性。例如,恶意用户可以大写 view
或 pure
关键字的第一个字母,使用同形异义字或使用其他技巧来欺骗用户,使他们相信函数是非变异的,即使该宏将该函数视为状态改变。如果没有检测到有效的可变性关键字,则会回退到将该函数视为状态改变,从而为意外错误和难以检测的诈骗打开了大门。
为了缓解此问题,请考虑在此宏中实施更严格的验证,以确保仅允许正确的可变性关键字。这将有助于防止故意滥用和意外错误,从而提高宏的整体安全性和可靠性。
更新: 已在 commit a474666 中解决。
Stylus 允许开发人员使用 selector
属性修改给定函数的选择器,该属性可以接受字符串名称
或 u32 ID
。此功能有助于在保持相同选择器的同时更改函数的名称,从而模拟 Solidity 重载功能并支持创建与语言无关的合约标准。
但是,提供 ID
可能会导致函数选择器同时存在于实现合约及其代理合约中。因此,用户可以使用与预期实现合约函数匹配的选择器来调用代理合约函数,从而导致意外的代码执行并阻止用户访问实现合约的功能。如果以某种方式定义 ID
整数,当转换为十六进制时,它与另一个合约中的函数选择器匹配(无论是实现还是代理),则可能发生此情况。
此漏洞使恶意项目能够创建难以检测的后门。与 Stylus 相比,Solidity 需要找到具有匹配选择器的函数签名才能利用此漏洞,这并非易事。即使找到此类函数签名,它们也可能由于代码库中无意义的名称而引起警惕。在 Stylus 中,当使用 name
选项而不是 ID
时,这种攻击更难执行,这使得此类诈骗更容易被发现。
自定义选择器还会混淆使用函数选择器来识别特定函数的第三方监控或索引服务。这些服务可能依赖于标准选择器,标准选择器是标准的一部分或属于社区数据库,例如 4byte 目录。如果合约使用自定义选择器,这些服务可能无法识别和监控事务,从而导致错误。
考虑到这些风险,请重新考虑 ID
选项,并考虑仅接受 name
。如果自定义选择器的优点不足以抵消风险,请考虑完全删除它们。
更新: 已在 commit 795d376 中解决。
在 Stylus 中,当继承合约覆盖其父合约中的基本函数时,没有检查来强制执行任何可变性规则。例如:
view
函数)可以被子合约中修改状态的函数覆盖。payable
的函数可以被子合约中的非 payable 函数覆盖,导致子函数在接收 ETH 时恢复。从开发人员的角度来看,这可能导致意外行为和容易出错的合约开发,因为这与 Solidity 中强制执行的规则不匹配,Solidity 强制执行更严格的可变性。为了与 Solidity 的可变性规则保持一致并避免意外行为,请考虑通过在父函数中实现对可变性属性的检查来重构 external
宏 中的逻辑。
更新: 已在 commit 1984d8a 中解决。
sol_interface
块中的多个接口定义重复函数如果同一个 sol_interface!
块中存在两个或多个接口定义,则后续接口将扩展为包含来自先前接口的方法定义。例如:
sol_interface! {
interface IService {
function makePayment(address user) payable returns (string);
function getConstant() pure returns (bytes32);
}
interface ITree {}
}
ITree
的扩展 Rust 接口将包括 make_payment
和 get_constant
,例如,允许在合约逻辑中调用 ITree.make_payment
。这可能允许恶意开发人员隐藏 ITree
的接口,包括先前接口定义中的函数,然后可以在后续接口上调用这些函数。此外,如果 ITree
包括其自己的合法定义 makePayment
,则代码将无法编译,因为实现块将包括相同名称的两个函数定义。
考虑修改 sol_interface
宏,方法是将 method_impls
声明移动 第一个 for
循环 内,以便它在迭代期间不会保留来自先前方法的Token。
更新: 已在 commit a64c058 中解决。
export-abi
功能编译可以使用 Stylus SDK 中的 export-abi
功能,通过合约 crate 中的 cargo stylus export-abi
命令为 Stylus 合约生成 Solidity ABI。使用此功能时,返回语句 在 [#external]
宏中被跳过,以便可以将 fmt_abi
添加到返回的 router
实现中。Stylus 语言支持诸如 StorageU1
和 StorageI1
这样的存储类型,这些类型在 Solidity 中并不存在。这些类型在 SDK 中没有提供任何明显的好处,并且无法与数组和向量一起正常工作。当与这些存储类型交互时,大量用于数组和向量的 density
函数会触发除零错误。此外,诸如 StorageBlockHash
和 StorageBlockNumber
这样的类型也缺乏明确的实用性。
考虑提供这些存储类型的用例和相关性的详细解释。如果无法证明它们的优势,建议删除它们,以避免混淆和潜在的互操作性错误。
加粗更新:已在 commit 3f511fa 中解决。
Stylus 允许在合约存储中使用字符串和动态大小的字节。但是,当前实现对于这些类型来说效率很低。具体来说,用于设置(例如,extend
)和检索(例如,get_bytes
)字符串或动态字节的函数按字节操作,这会导致大量的 SLOAD
和 SSTORE
操作。这种低效率在使用较长的字符串或字节数组时尤其明显,从而导致 gas 成本显著增加。
考虑重构字符串和动态字节的存储操作,以优化 gas 的使用。可能的方法包括在适当的时候预先分配存储空间;尽可能以完整的 32 字节字写入数据;以及最大限度地减少存储操作的数量。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
正在进行此问题的工作,但需要一些额外的测试。
Stylus SDK 允许开发人员使用 Rust 编程语言为 Arbitrum 链创建智能合约,然后将其编译为 WASM 并与 Solidity 合约一起部署。但是,最终的 WASM 输出受到多种因素的影响,包括 Rust 版本、启用的特性、依赖项等等。这些变化使得构建过程在不同的操作系统和架构上几乎是非确定性的。这种非确定性使合约验证变得复杂,而合约验证对于建立合约的信任和可靠性至关重要。如果没有一致的构建输出,就很难确保部署的 WASM 准确地反映了预期合约的代码。恶意行为者可以通过修改 SDK 来编译无法按预期运行的 WASM 文件来利用这一点,即使智能合约代码看起来是安全的。
考虑通过指定和执行明确的指南,并在开发周期的早期(而不是在部署后)向开发人员发出通知来标准化构建过程。这将简化合约验证并增强用户对合约完整性的信任。
加粗更新:已在 commit be51b58 中解决。修复已在 cargo-stylus
存储库上进行。
当前工作区仅包含对 abi
模块和 mini-alloc
crate 的有限数量的单元测试。代码库的其余部分完全缺乏单元测试以及任何集成测试。这种有限的测试覆盖可能会导致未被发现的问题,并阻碍跨各种模块的代码功能验证。
考虑添加一个强大的测试套件,其中包括所有模块的全面单元和集成测试。这将有助于确保系统不同部分之间的正确交互。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
我们目前正在为 SDK 添加一套完整的单元测试,以及 issue #148 中概述的其他项目。
receive
和 fallback
函数在 Stylus 中缺少 receive
和 fallback
函数(一种旨在与 Solidity 互操作的语言)可能会产生几个重要的影响。在 Solidity 中,receive
函数处理直接将 ETH 转移到合约。如果没有此函数,合约将无法接受纯 ETH 转移,从而将 ETH 转移限制为包含触发特定函数调用的数据的那些转移。例如,由于缺少 receive
函数,诸如 PaymentSplitter
这样的合约仅适用于外部拥有的帐户 (EOA)。此外,许多代理模式依赖于 fallback
函数将调用转发到另一个合约。如果没有 fallback
函数,实现可升级合约或信标代理模式会变得更加复杂,需要替代机制将调用委托给其他合约。
考虑实现 receive
和 fallback
函数,以确保与 Solidity 合约的完全兼容。这样做将有助于提高代码灵活性并支持不同的代理模式。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
正在实施中。需要进一步测试。进度可以在 issue #150 上跟踪。
Stylus 智能合约中的 sol_interface
宏允许开发人员几乎复制和粘贴 Solidity 接口,以实现无缝的合约交互。但是,当前实现存在一些潜在的缺陷。虽然它允许接口中的接口继承、事件和错误等元素,但这些元素会被静默忽略,只处理函数。这种行为可能会导致混淆和错误,因为合约在编译时不会出现问题,尽管存在这些不受支持的元素。
为了解决这个问题,考虑记录 sol_interface
宏的当前限制,以设定适当的用户期望,并实施检查以在检测到不受支持的语法时恢复。如果可行,请考虑在未来的更新中包含这些附加功能,因为它可以增强整体代码功能。
加粗更新:已在 commit 821b7f6 和 be6306c 中解决。
在 Stylus 中,可以使用诸如 #[view]
、#[write]
和 #[pure]
之类的属性来标记合约方法,以明确定义它们如何与合约状态交互。但是,恶意用户可以通过两种方式来误导用户或第三方服务关于这些属性:
#[::pure]
、#[stylus::view]
或任何其他带有冒号的名称,以绕过 external
宏强制执行的检查。这允许他们歪曲函数的意图。#[pure]
和 #[view]
方法)可以错误地标记为 #[write]
属性,甚至不需要冒号。该宏的内联文档包含此信息,但应修复。为了防止这些问题,请考虑向 external
宏添加适当的验证,以强制正确使用这些属性。
加粗更新:已在 commit d44d94f 中解决。Offchain Labs 团队删除了可变性说明符(除了 #[payable]
之外),因为它们可以从 &self
/ &mut self
或缺少 self
中推断出来。此更改通过利用 Rust 的语法简化了代码。由于使用 &self
的方法或没有 self
的方法仍然可以通过外部调用修改其他合约的状态(甚至它们自己的状态,如果启用了重入),因此该团队已在代码库中彻底记录了此行为,以告知用户。
Call
的不明确文档在文档和代码注释中存在多个不明确或与 Call::new
和 Call:new_in
之间的差异相矛盾的实例。例如,Call::new
被给出一个简单的例子来展示如何配置 gas 和 value。但是,new
仅在启用重入标志时可用。这背后的原因尚不清楚,因为此注释说 new_in
应该用于重入调用。文档中的其他令人困惑的注释包括:
另请注意,应使用 Call::new_in 代替 Call::new,因为前者提供对存储的访问。先前使用禁用重入编译的代码可能需要修改才能进行类型检查。这样做是为了确保存储更改被持久化,并且在调用之前正确管理存储缓存。
考虑清楚地记录 Call::new
和 Call::new_in
之间的区别,以及它们代表什么存储访问模式,并相应地修改代码注释。
加粗更新:已在 commit ba3472f 中解决。
要使用 Call::new_in
设置调用上下文,storage
参数必须实现 TopLevelStorage
。创建此实现的常用模式是使用 entrypoint
宏,但如果一个 crate 中有多个合约,则并不总是需要这样做。如果没有 TopLevelStorage
,&self
和 &mut self
将不再可用,并且需要繁琐的解决方法,例如为 TopLevelStorage
添加一个空实现。例如:
unsafe impl TopLevelStorage for Contract {}
此外,当使用 Call::new_in
时,storage
参数的目的是什么尚不清楚,因为 call
函数永远不会实际使用调用上下文的此属性。
考虑更新代码注释和文档,以清楚地描述 storage
参数的目的。此外,考虑 Call::new_in
函数的替代实现,以适应未实现 TopLevelStorage
trait 的合约。
加粗更新:已在 commit ba3472f 中解决。
在整个代码库中,发现了多个不准确或具有误导性的文档实例:
StorageBlockNumber
中 set
函数的内联文档与 get
方法的文档相同,导致混淆它们的不同功能。raw_log
函数的文档建议用户优先使用 alloy 类型的 raw_log
,但实际上应该建议使用 log
函数。考虑更正文档以与代码行为保持一致。这将有助于提高代码库的清晰度和可读性。
加粗更新:已在 commit dc1e9c5 中解决。
Stylus 合约的 WASM 输出包括敏感元数据,例如编译合约的个人的用户名和来自主目录的部分路径。尽管此信息不会构成直接的安全风险,但攻击者可以利用它进行社会工程或其他有针对性的攻击。
考虑从生产版本中删除或混淆此类信息,以维护隐私并减少潜在的攻击面。从 WASM 输出中剥离元数据可以帮助缓解这些风险,而不会影响已部署合约的功能。
加粗更新:已在 commit 00abf34 中解决。修复已在 cargo-stylus
存储库上进行。
在 Stylus 合约中,来自 SDK 的 mini-alloc
分配器模块由于其性能而旨在成为标准分配器。此模块包含在 SDK 提供的每个示例和默认模板中,确保开发人员朝着使用最有效选项的方向前进。但是,指定使用 mini-alloc
的全局分配器的声明可能会被无意中删除。如果发生这种情况,合约将默认使用标准库中的分配器,该分配器的效率明显较低。因此,使用此分配器部署的合约的 gas 效率将非常低。
考虑强制 mini-alloc
作为所有 Stylus 合约的默认分配器。仅应通过有意的且明显的动作来允许更改分配器,以避免意外回退到效率较低的标准库分配器。
加粗更新:已在 commit 2354799 中解决。
在 stylus-proc
文件夹中,保存每个宏的实现的文件缺少适当的文档字符串。
鉴于代码的复杂性和长度,请考虑添加详细的文档字符串。这将使读者和开发人员更容易更好地理解代码库的内部工作原理,同时提高整体代码可维护性。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
我们正在寻求重构我们的程序宏实现,以使其更容易测试和更容易理解。这将包括对其实现的更好的内部文档。可以在 issue #151 上跟踪进度
RawDeploy
中具有误导性的方法limit_revert_data
和 skip_revert_data
方法设置 RawDeploy
实例的 offset
和 size
字段。但是,这些字段从未使用在 deploy
函数中,导致这些方法是多余的。这个问题很重要,因为它误导开发人员相信他们可以控制返回的 revert 数据量,而实际上,这些方法对部署结果没有影响。
考虑删除这些方法和字段,或者修改 deploy
函数以使用这些字段。
加粗更新:已在 commit 6e21166 中解决。
\#[borrow]
属性的潜在误用#[borrow]
属性用于存储字段,以实现特定类型的 Borrow
和 BorrowMut
trait,从而促进 Stylus 合约中的继承。但是,它的应用可以扩展到不代表父合约存储的状态变量,从而导致问题。
主要问题是当 #[borrow]
用于简单类型时的不正确的语义含义。此属性是为代表合约存储子集的复杂类型设计的,而不是为单个存储槽设计的。这种用法歪曲了属性的目的,误导开发人员关于存储布局或合约组成。此外,它会生成简单存储类型不需要的额外 trait 实现,导致不必要的代码膨胀,并可能在没有任何好处的情况下增加合约大小。
考虑仅允许将 #[borrow]
属性用于真正代表合约存储子集的字段。这确保了准确的语义表示,同时避免了具有误导性的代码和不必要的 trait 实现。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
正在进行此问题的工作,我们正在寻找一种在 Rust 中可行的解决方案。可以在 issue #149 上跟踪进度
sol_interface
宏中的 constant
状态可变性sol_interface
宏当前允许用户定义带有标记为 constant
的方法的 Solidity 接口,用于状态可变性。但是,从 Solidity 0.5.0 版本开始,不再支持函数的 constant
关键字,并且已被 view
和 pure
替换。虽然该宏在内部将带有 constant
关键字的函数转换为 pure
,但保留 constant
关键字可能会产生误导。
为与更高版本的 Solidity 保持一致并避免错误的可变性假设,请考虑更新 sol_interface
宏以禁止使用 constant
关键字来表示函数状态可变性。此更改将有助于确保与现代 Solidity 版本兼容并增强代码正确性。
加粗更新:已在 commit e173182 中解决。
sol_interface
对函数可见性的不正确处理在 Solidity 中,必须在接口中指定函数的可见性,并且它应该始终是 external
。Stylus 中的 sol_interface
宏(处理这些 Solidity 接口)当前允许用户包含具有不正确可见性属性(例如 public
)的方法,或者完全省略可见性属性。这种不符合 Solidity 标准的行为可能会导致开发人员感到困惑。
考虑添加验证以确保 external
关键字出现在函数定义中。这将与 Solidity 的接口要求保持一致,并防止潜在的误解。
加粗更新:已在 commit 2330ac7 中解决。
sol_interface
缺乏对 Struct 和 Enum 类型的支持sol_interface
宏旨在使开发人员能够使用其本机接口从 Stylus 智能合约无缝调用 Solidity 合约。但是,它目前不支持 struct 和 enum 类型,这些类型在 Solidity 中通常使用。此限制迫使开发人员使用可读性较差的解决方法,从而可能导致意外错误并降低代码可维护性。
考虑实现对 struct 和 enum 的支持。这将显着增强开发人员体验并确保与 Solidity 接口的完全兼容。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
已开始在
sol_interface!
中支持 struct。发布需要更多测试,并且还应实施 enum。可以在 issue #74 中跟踪进度。
sol_storage!
宏不支持私有状态变量私有状态变量通过限制直接访问来帮助强制执行封装,确保仅通过受控函数修改变量。这种方法最大限度地减少了攻击面,并防止了意料之外的副作用或不一致。
目前,当使用 sol_storage!
宏时,无法将状态变量设置为私有,允许子合约直接访问和修改这些变量。开发人员必须改用 #[solidity_storage]
宏,该宏支持私有状态变量。
为了与 #[solidity_storage]
保持一致,请考虑增强 sol_storage!
宏以支持私有状态变量。或者,此限制应在官方文档中清楚地记录。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
我们将考虑将私有状态变量用于
sol_storage!
宏,作为 issue #147 跟踪的 N-04 工作的一部分。
在整个代码库中,发现了多个可以重命名以更好地反映其目的的元素实例:
emit_log
函数中的 topics
参数应重命名为 number_topics
,以更好地反映它所代表的值的性质。#[external]
宏应重命名为 #[public]
。当前名称可能会与 Solidity 的 external
可见性混淆,这意味着该函数无法从合约内部调用。#[solidity_storage]
宏应重命名为 #[persistent_storage]
、#[storage]
或 #[state]
。相反,当前名为 sol_storage
的包装宏应采用名称 solidity_storage
,因为它与 Solidity 的语法更相关。types
模块名称表明存在多种类型。但是,如果只定义了 Address
,则应将模块重命名为 AddressType
。或者,如果预计会添加其他常见类型,请考虑指定这些类型以避免混淆。考虑解决这些命名问题以提高代码库的可读性。
加粗更新:已在 commit 8d1699f 中解决。Offchain Labs 团队决定保留 sol_storage!
宏和 types
模块的名称。前者与来自 alloy 的 sol!
宏对齐,而后者将在即将发布的版本中包含其他类型。
wee_alloc
Crate 未维护且易受攻击wee_alloc
crate 是 WebAssembly 的最小分配器,已不再积极维护,上次发布是在三年多前。此外,其两位维护人员已表示他们不打算继续支持该 crate。因此,几个未解决的 open issue,包括内存泄漏。
即使当前未将该 crate 用于 wasm32
目标,也请考虑切换到更积极维护且更安全的替代方案,例如 lol_alloc
或默认的 Rust 标准分配器。
加粗更新:已在 commit 80bfcba 中解决。
范围内的几乎每个文件顶部的许可证 URL 注释都指向 GitHub 存储库中的特定分支(stylus
)。此分支不是主分支,将来可能会更改或删除。如果分支名称更改或许可证重新定位,则当前 URL 引用将变为无效,从而导致链接断开。
考虑更新许可证 URL 以指向更稳定的引用,例如特定的 commit 或标签。或者,考虑包含一条注释,说明分支名称可能会更改。这些措施将有助于确保 URL 随着时间的推移保持功能。
加粗更新:已在 commit 0a0ace1 中解决。
sol_storage
宏中的有限功能sol_storage
宏当前允许用户在其智能合约中使用类似 Solidity 的语法定义状态变量。但是,它并未完全复制 Solidity 的语法,尤其是在缺乏指定可见性以及定义常量和不可变对象方面的支持。此限制会阻止诸如使用 public
关键字自动生成 getter 之类的事情,并使其难以像在 Solidity 中一样无缝地定义常量和不可变对象。
考虑扩展 sol_storage
宏的功能以支持这些功能。利用 syn_solidity
crate 可以通过提供对类似 Solidity 语法的更全面的解析和处理来促进此增强。
加粗更新:已确认,但未解决。Offchain Labs 团队表示:
我们正在考虑将这些附加功能添加到
sol_storage!
宏中。可以在 issue #147 中跟踪进度。
过时的版权年份可能无法反映近期的修改或持续的开发。代码库中的多个文件具有过时的版权年份,包括 license 文件。其他示例包括:
考虑更新所有过时的版权信息,以表明正在积极维护并关注细节。
更新: 已在提交 d57458d 中解决。
在开发过程中,拥有描述完善的 TODO 注释将使跟踪和解决它们的过程更加容易。如果没有这些信息,这些注释可能会随着时间的推移而变得过时,并且在发布到生产环境时,可能会忘记对系统安全至关重要的信息。这些注释应在项目的 issue 待办事项中跟踪,并在系统部署之前解决。
在整个代码库中,发现了多个 TODO 注释的实例:
考虑删除所有 TODO 注释的实例,而是在 issue 待办事项中跟踪它们。或者,考虑将每个内联 TODO 注释链接到相应的 issue 待办事项条目。
更新: 已在提交 73dbc1c 中解决。
Stylus SDK 允许智能合约开发者使用 Rust 语言为 Arbitrum 生态系统构建应用程序。这些 Stylus 程序会被编译成 WebAssembly (WASM),并且可以部署在链上,与 Solidity 智能合约一起运行。这种创新方法旨在将 WASM 执行的效率与 Rust 编程的健壮性相结合,同时保持与以太坊虚拟机 (Ethereum Virtual Machine) 的兼容性。
在对 Stylus SDK 进行安全审计期间,我们发现了许多安全问题,并对整体设计的改进提出了广泛的建议。该项目显然仍在开发中,具有多个非功能性或包含错误的功能。但是,开发团队表现出对解决这些问题的坚定承诺,我们鼓励他们继续努力。一旦所有已识别的问题都得到解决,并且进行了进一步的改进,并且该项目达到了更成熟的阶段,我们强烈建议该团队考虑进行后续审计,以确保全面的安全性。
尽管目前面临挑战,但我们看到了 Stylus SDK 的巨大潜力。我们期待看到该项目的发展,特别是当团队解决已发现的问题并继续完善 SDK 时。我们相信,Stylus SDK 的进一步开发可能会为 Arbitrum 生态系统和更广阔的智能合约开发领域带来令人兴奋的新可能性。
- 原文链接: blog.openzeppelin.com/st...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!