本文对OpenZeppelin的Stellar Contracts Library进行了审计,重点分析了其可用性和安全性,识别出若干高、中、低严重性的问题,并提出优化建议。审计结果显示,库在开发过程中的灵活性和安全性设计良好,适用于Stellar区块链上智能合约的开发。
TypeLibraryTimelineFrom 2025-02-03To 2025-02-07LanguagesRust (Soroban)Total Issues15 (14 resolved)Critical Severity Issues0 (0 resolved)High Severity Issues1 (1 resolved)Medium Severity Issues1 (1 resolved)Low Severity Issues5 (4 resolved)Notes & Additional Information7 (7 resolved)Client Reported Issues1 (1 resolved)
我们审核了OpenZeppelin/stellar-contracts库的提交01dbcb5。
审核范围包括以下文件:
contracts
├── token/fungible/src
│ ├── extensions
│ │ ├── burnable
│ │ │ ├── mod.rs
│ │ │ └── storage.rs
│ │ ├── metadata
│ │ │ ├── mod.rs
│ │ │ └── storage.rs
│ │ ├── mintable
│ │ │ ├── mod.rs
│ │ │ └── storage.rs
│ │ └── mod.rs
│ ├── fungible.rs
│ ├── lib.rs
│ └── storage.rs
└── utils
├── pausable
│ └── src
│ ├── lib.rs
│ ├── pausable.rs
│ └── storage.rs
└── pausable-macros
└── src
├── helper.rs
└── lib.rs
Soroban主网于2024年2月推出,使得能够将智能合约部署到Stellar区块链。这标志着Stellar生态系统发展的一个关键时刻。一年后,Stellar开发基金会(SDF)与OpenZeppelin联手,旨在简化和加速在Soroban上构建项目的开发过程。
Stellar合约库是一组专注的智能合约组件,旨在促进稳健、安全和高效的去中心化应用程序的开发。本次审计检查了该库的第一个发布候选版本,目前包括两个关键模块:可替代代币及其扩展,以及可暂停的实用程序。
根据SEP-0041标准开发的openzeppelin_fungible_token
crate包含基本的代币扩展:可销毁、元数据和可铸造,下面将对此进行更详细的描述。可替代代币合约模块实现了在Soroban合约中管理可替代代币的实用程序。它提供了基本的存储能力,以管理代币余额、授权和总供应量。
该模块旨在灵活性,功能分为两个层次。高级功能封装所有必要的检查、验证、授权、状态更改和事件发射,允许用户执行标准的代币操作而无需深入底层复杂性。相比之下,低级功能则为希望构建自定义工作流程的开发者提供了细粒度控制,暴露需要手动处理的验证和授权的内部机制。
FungibleBurnable
特征可以用来扩展FungibleToken
特征,以提供销毁代币的能力。burnable
模块包含三个函数:burn
和burn_from
,负责从特定账户中移除代币,emit_burn
负责发出burn
事件。
FungibleMintable
特征可以用来扩展FungibleToken
特征,以提供铸造代币的能力。该特征旨在与FungibleToken
特征配合使用。mintable
模块包含mint
函数,创建特定账户的新代币,以及emit_mint
函数,发出mint
事件。
与其他扩展不同,metadata
没有提供单独的特征,因为相应的函数已经在FungibleToken
特征中可用。它包含负责设置和返回与代币相关的元数据的函数,如符号、名称和小数。
该模块提供了强大的机制,确保在紧急情况下可以管理合约状态,使项目能够有效应对关键情况。它包括Pausable
特征,这个特征是任何处理暂停和恢复的合约所需实现的。另外,还提供两个辅助属性宏when_paused
和when_not_paused
,作为执行门,检查合约是否已暂停后再进行执行。
Stellar合约库基本上依赖于Soroban SDK和支持该库的各种crate,以便安全地开发宏。我们假设这些超出范围的依赖项是安全的并且得到了积极的维护。此外,OpenZeppelin合约库旨在提供最大灵活性。因此,用户需根据建议的指导方针集成其功能,并在自定义实现时务必小心,因为此类修改可能引入脆弱性。
when_not_paused
和 when_paused
属性宏在函数开始插入一个暂停检查,以确定合约是否已暂停。它们的输出块中 [1] [2] 处理已使用的函数,保留其可见性和签名,然后在返回函数主体之前应用适当的暂停检查。
然而,生成的输出并没有保留后续属性。因此,当函数被注释为when_not_paused
或when_paused
后又跟随附加属性时,这些属性将会被省略。这可能导致意外行为,例如,当when_paused
后面跟随only_owner
以限制暂停后的合约所有者访问时。由于only_owner
在when_paused
之后出现,因此它会被忽略,可能导致未授权访问仅应为所有者的函数。
为防止此问题的发生,请考虑修改宏逻辑,以保留并正确应用生成函数输出时的后续属性。
更新:在pull request #29中解决,提交为ff978d3。
在openzeppelin_fungible_token
crate中定义的approve
函数 授权支出者从所有者的账户支出amount
的代币。该批准在live_until_ledger
参数中指定的分类帐号之前有效。该值与当前分类帐号之间的差异随后用于扩展批准的条目TTL。然而,每个条目的最大时间段 最大TTL 每次只能按时间扩展,如果live_until_ledger
参数与当前分类帐号之间的差异高于该值,则代码将崩溃,并且不会进行批准。因此,无法批准代币的时间段长于最大TTL值。
考虑允许账户指定的批准在超过最大TTL值的情况下有效,方法是将TTL扩展期限限制为最大可能值。应明确记录,延长包含批准数据的存储条目TTL以确保未经批准对live_until_ledger
到期是由批准账户或批准接收方负责,或者考虑使用permanent
存储来储存允许。
更新:在pull request #57中解决,提交为269a679,通过记录限制批准期间为最大TTL值的设计选择。值得注意的是,当前的批准逻辑与Stellar资产合约(SAC)略有不同,因为批准存储条目的TTL通过整个批准期间而不是如SAC中增加1来扩展。然而,SAC中的该逻辑可能在未来发生变化,因此不再像之前那样以1的方式增加批准存储条目的TTL,如在此问题中所述。
Env
类型检查可以被绕过在openzeppelin_pausable_macros
crate中,check_is_env
函数 检查函数的第一个参数是否为soroban_sdk::Env
类型。然而,该检查目前只依赖于类型名称"Env",这使其容易被绕过。例如,如果Env
被重命名(alias)(例如,use soroban_sdk::{Env as Environment}
),检查将失败,因为类型名称"Environment"
与"Env"
不匹配。此外,从其他crate中使用Env
类型时(例如,use external_crate::Env
),或者将一个无关类型重命名为Env
(例如,use external_crate::{RandomType as Env}
)时也会出现问题。
为了确保准确性,请考虑解析参数类型的全路径,以明确验证其是否匹配soroban_sdk::Env
。
更新:已认定,但未解决。OpenZeppelin团队表示:
_Rust中的过程宏不能强制类型安全。在这种情况下,宏无法强制
e: &Env
属于soroban_sdk::Env
,因为宏只能访问其注释的范围,并且不能访问代码的其他部分,因此宏无法查看导入,也无法在展开时分析整个类型。防止覆盖或重命名Env
变量的责任属于开发者,而不是宏。_
在代码库中发现了多处误导性文档:
openzeppelin_fungible_token
crate的lib.rs
文件中,文档声明Mintable
允许授权实体铸造。然而,mint
函数的文档明确指出,它缺乏授权控制,授权是实现者的责任。approve
函数缺少FungibleTokenError::LessThanZero
关于错误的文档。openzeppelin_fungible_token::extensions::mintable
crate中,storage.rs
中第31行的注释假设require_auth
实现了Try
特征,通过使用?
进行错误处理,这样的假设是不正确的。burn
函数缺少FungibleTokenError::LessThanOrEqualToZero
的错误文档。burn_from
函数同样如此。在这里。burn
和burn_from
函数的文档声明它们从account
中销毁amount
的代币,而实际上,它们没有account
参数,而是从from
账户中销毁amount
的代币。FungibleBurnable
和FungibleMintable
特征将它们称为Burnable
和Mintable
,而没有包含“可替代”前缀。请考虑解决上述误导性文档实例,以提高代码库的清晰度和可维护性。
更新:在pull request #50中解决,提交为1388d4d。
在openzeppelin_fungible_token
crate中定义的update
辅助函数 负责根据提供的参数更新代币余额和代币总供应量。然而,当指定的代币数量等于0时,该函数会崩溃。因此,0代币的转账是不可能的。虽然这并不是由SEP-41标准所要求的,但在EVM兼容区块链上被广泛使用的ERC-20标准要求此类转账是可能的。
请考虑允许0代币的转账,以为利用这些代币的合约提供更多灵活性。
更新:在pull request #48中解决,提交为129dfe1。
每当对代币合约中的instance
存储执行更改数据的操作时,例如铸造或销毁代币,该instance
的TTL 都会增加。然而,执行在代币合约上的其他操作,例如查询余额或在账户之间转账,不会增加instance
的TTL。因此,有可能代币合约正在被使用,但其数据最终会归档。因此,必须手动恢复,从而产生额外费用。
请考虑在所有查询或更改合约状态的操作中增加合约的instance
存储TTL。或者,考虑记录当前的设计选择,以便实现者清楚它的意图。
更新:在pull request #62中解决,提交为4500bfb。
在openzeppelin_fungible_token
crate中实现的spend_allowance
辅助函数 从owner
的授权中扣除给定数量的代币。然而,它没有检查指定的amount
是否为负数。因此,在这种情况下不会引发崩溃。
只要此函数与执行此检查的update
函数一起使用,就不会造成任何问题。但如果spend_allowance
函数在不同的上下文中使用,可能与不同的update
函数实现一起使用,这可能导致在代币数量出现不正确时造成意外结果。
考虑通过在函数内部崩溃,以使spend_allowance
函数更加自给自足,当指定amount
为负数时。
更新:在pull request #49中解决,提交为12f81e6。
when_not_paused
和when_paused
宏包含几乎相同的代码,导致冗余。
为提高可维护性、可读性和简洁性,请考虑重构这些宏并减少代码重复。
更新:在pull request #29中解决,提交为544354d。
在allowance_data
函数中,default
变量在没有允许的情况下返回。然而,在返回允许值时,default
变量保持未使用状态。
考虑删除不必要的变量声明,以提高代码的清晰度和效率。
更新:在pull request #56中解决,提交为615e13d。
在openzeppelin_fungible_token
crate中,allowance
函数 应该在live_until_ledger
值小于当前分类帐号时返回0。在第105行的代码中检查live_until_ledger
值是否小于当前分类帐号且允许大于0的情况。后者检查是不必要的,可以删除,因为它对逻辑和函数的结果没有影响。
考虑删除任何不必要的检查,以提高代码的清晰度。
更新:在pull request #55中解决,提交为778a6e7。
在openzeppelin_fungible_token::extensions::metadata
crate中,get_metadata
函数被用来返回代币元数据,如小数、名称和符号。该函数尝试从存储中获取元数据,如果没有找到值,则会崩溃,使用unwrap_optimized
。通常,最好使用unwrap_or
或类似的错误处理函数返回一个默认值。然而,在这种情况下,返回一个固定的默认值可能在某些情况下造成误导,并可能改变代币的行为。此外,预计在部署时使用set_metadata
函数在构造函数中始终设置元数据。
为防止返回默认值,请考虑使用expect
解包该值,从而在出现错误时还原更具描述性的错误,方便调试。
更新:在pull request #54中解决,提交为5737528。
在整个代码库中发现多处拼写错误:
token/fungible/src/fungible.rs
的第11行中,“have”应为“has”。token/fungible/src/extensions/burnable/mod.rs
的第61行中,“A”在行末可以删除。token/fungible/src/storage.rs
的第379行中,“amoount”应为“amount”。token/fungible/src/extensions/burnable/storage.rs
的第37行中,amount
与“is”之间没有空格。请考虑纠正所有拼写错误,以提高代码库的清晰度和可读性。
更新:在pull request #52中解决,提交为51e84b0。
可暂停模块的文档包含一个声明,可使其更正式。此外,以下一行中的“due to”可以更改为“由于以下原因”以提高可读性。
为了提高代码库的可读性,请考虑重写上述注释。
更新:在pull request #53中解决,提交为23a21e6。
在整个代码库中,通常在使用panic_with_error
宏时不在行末加入分号。然而,在openzeppelin_fungible_token
crate的storage.rs
中,有三个实例[1] [2] [3] 包含分号。虽然这没有造成安全风险,但它引入了代码不一致性,并可能引起混淆。
为了增强一致性和可读性,请考虑在整个代码库中采用统一的格式风格。
更新:在pull request #73中解决,提交为29696bc。
在openzeppelin_fungible_token
和openzeppelin_pausable
crate中定义的FungibleTokenError
和PausableError
错误枚举不符合文档要求。特别是,错误定义未使用#[repr(u32)]
和#[derive(Copy)]
属性。
为了遵循最佳实践,请在定义合约错误时考虑实现文档建议的衍生宏,使用#[contracterror]
属性进行标记。
更新:在pull request #31中解决,提交为7342590。
Stellar合约库的第一个发布候选版本是对通用合约,特别是可替代代币和可暂停实用程序进行标准化的初步努力,旨在简化开发并促进Soroban上的应用。总体而言,代码库简洁,遵循良好的编码实践,并 включает обширную документацию и тестирование.
在审计过程中,发现了一个高严重性问题。Soroban合约团队响应迅速,积极参与有关设计决策的讨论,并为库的发展提供了宝贵的见解。我们感谢SDF致力于创建更易开发的环境,最终将使整个生态系统受益。
- 原文链接: blog.openzeppelin.com/st...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!