Stellar合约库0.1.0审计

本文对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模块包含三个函数:burnburn_from,负责从特定账户中移除代币,emit_burn负责发出burn事件。

可铸造

FungibleMintable特征可以用来扩展FungibleToken特征,以提供铸造代币的能力。该特征旨在与FungibleToken特征配合使用。mintable模块包含mint函数,创建特定账户的新代币,以及emit_mint函数,发出mint事件。

元数据

与其他扩展不同,metadata没有提供单独的特征,因为相应的函数已经在FungibleToken特征中可用。它包含负责设置和返回与代币相关的元数据的函数,如符号、名称和小数。

可暂停的实用程序

该模块提供了强大的机制,确保在紧急情况下可以管理合约状态,使项目能够有效应对关键情况。它包括Pausable特征,这个特征是任何处理暂停和恢复的合约所需实现的。另外,还提供两个辅助属性宏when_pausedwhen_not_paused,作为执行门,检查合约是否已暂停后再进行执行。

安全模型和信任假设

Stellar合约库基本上依赖于Soroban SDK和支持该库的各种crate,以便安全地开发宏。我们假设这些超出范围的依赖项是安全的并且得到了积极的维护。此外,OpenZeppelin合约库旨在提供最大灵活性。因此,用户需根据建议的指导方针集成其功能,并在自定义实现时务必小心,因为此类修改可能引入脆弱性。

高严重性

属性宏省略后续属性

when_not_pausedwhen_paused 属性宏在函数开始插入一个暂停检查,以确定合约是否已暂停。它们的输出块中 [1] [2] 处理已使用的函数,保留其可见性和签名,然后在返回函数主体之前应用适当的暂停检查。

然而,生成的输出并没有保留后续属性。因此,当函数被注释为when_not_pausedwhen_paused后又跟随附加属性时,这些属性将会被省略。这可能导致意外行为,例如,当when_paused后面跟随only_owner以限制暂停后的合约所有者访问时。由于only_ownerwhen_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函数同样如此。在这里
  • burnburn_from函数的文档声明它们从account中销毁amount的代币,而实际上,它们没有account参数,而是从from账户中销毁amount的代币。
  • FungibleBurnableFungibleMintable特征将它们称为BurnableMintable,而没有包含“可替代”前缀。

请考虑解决上述误导性文档实例,以提高代码库的清晰度和可维护性。

更新:pull request #50中解决,提交为1388d4d

0数量的转账不可行

openzeppelin_fungible_token crate中定义的update辅助函数 负责根据提供的参数更新代币余额和代币总供应量。然而,当指定的代币数量等于0时,该函数会崩溃。因此,0代币的转账是不可能的。虽然这并不是由SEP-41标准所要求的,但在EVM兼容区块链上被广泛使用的ERC-20标准要求此类转账是可能的

请考虑允许0代币的转账,以为利用这些代币的合约提供更多灵活性。

更新:pull request #48中解决,提交为129dfe1

实例TTL未在所有操作中更新

每当对代币合约中的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_pausedwhen_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_tokenopenzeppelin_pausable crate中定义的FungibleTokenErrorPausableError错误枚举不符合文档要求。特别是,错误定义未使用#[repr(u32)]#[derive(Copy)]属性。

为了遵循最佳实践,请在定义合约错误时考虑实现文档建议的衍生宏,使用#[contracterror]属性进行标记。

更新:pull request #31中解决,提交为7342590

结论

Stellar合约库的第一个发布候选版本是对通用合约,特别是可替代代币和可暂停实用程序进行标准化的初步努力,旨在简化开发并促进Soroban上的应用。总体而言,代码库简洁,遵循良好的编码实践,并 включает обширную документацию и тестирование.

在审计过程中,发现了一个高严重性问题。Soroban合约团队响应迅速,积极参与有关设计决策的讨论,并为库的发展提供了宝贵的见解。我们感谢SDF致力于创建更易开发的环境,最终将使整个生态系统受益。

  • 原文链接: blog.openzeppelin.com/st...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。