Yield VR 安全审查

本文是对 Yield Variable Rate 智能合约协议的安全审查报告,由 Christos Pap 完成。报告详细记录了在审查过程中发现的漏洞、问题和代码改进建议,包括高、中、低风险等级的问题,并提出了相应的修复或缓解措施。主要涉及合约代码的精度损失、不准确的退款逻辑以及与EIP3156标准兼容性等问题。

Yield VR 安全审查

Yield Variable Rate 智能合约协议的安全审查由 Christos Pap 完成。\ 此安全审查报告包括在安全审查期间发现的所有漏洞、问题和代码改进。

免责声明

“审计是一项受时间、资源和专业知识约束的工作,训练有素的专家使用自动化和手动技术相结合的方法来评估智能 合约,以尽可能多地发现漏洞。审计可以显示漏洞的存在,但不能证明其不存在。”

  • Secureum

关于 Christos

Christos Pap 是一位独立的安全性研究员,专长是 Ethereum 智能合约安全。他目前在 Spearbit 担任初级安全性研究员,并且是 yAcademy 奖学金计划的校友。此外,他还是电气和计算机工程专业的本科生,并且在进攻性安全方面拥有丰富的经验,曾担任渗透测试员并持有 OSCP 认证。你可以在 Twitter 上与他联系,@christos_eth

风险分类

严重程度 影响:高 影响:中等 影响:低
可能性:高 危急 中等
可能性:中等 中等
可能性:低 中等

影响

  • - 导致协议中大量资产的重大物质损失或严重损害了一组用户。
  • 中等 - 只有少量资金可能损失(例如价值泄漏)或协议的核心功能受到影响。
  • - 可能导致协议某些功能出现任何类型的意外行为,但不是很严重。

可能性

  • - 攻击路径可能,具有合理的假设,这些假设模仿链上条件,并且攻击成本相对于可以被盗或损失的资金量而言相对较低。
  • 中等 - 仅有条件地激励攻击向量,但仍然相对可能。
  • - 具有太多或太不可能的假设,或需要攻击者投入巨额赌注,但几乎没有或根本没有激励。

严重程度级别所需的操作

  • 危急 - 客户必须修复此问题。
  • - 客户必须修复此问题。
  • 中等 - 客户应该修复此问题。
  • - 客户可以修复此问题。

执行摘要

概述

项目名称 Yield 协议
仓库 https://github.com/yieldprotocol/vault-v2
Commit hash 1d1602a06fda352f463b6f126c8a90e05e221541
文档 https://docs.yieldprotocol.com/
方法 手动审查

发现的问题

严重程度 计数
危急风险 0
高风险 2
中等风险 1
低风险 3
信息 5

范围

文件 nSLOC
合约 (6)
src/variable/VRCauldron.sol 297
src/variable/VRLadle.sol 210
src/variable/VRRouter.sol 18
src/variable/VRWitch.sol 57
src/variable/VYToken.sol 154
src/oracles/VariableInterestRateOracle.sol 149
接口 (2)
src/variable/interfaces/IVRCauldron.sol 22
src/variable/interfaces/IVRWitch.sol 78
总计 (8) 985

发现

编号 标题 严重程度 解决方案
[H-01] VariableInterestRateOracle:get 函数中的精度损失会影响 Yield Variable Rate 协议的利率 已修复
[H-02] 不准确的退款逻辑导致底层 Join 合约中的错误会计 已修复
[M-01] VyToken 中的 NameSymbolDecimals 将具有默认值 中等 已修复
[L-01] 如果现货预言机出现故障,包括清算在内的大部分 Yield Variable Rate Protocol 可能会被冻结 已确认
[L-02] 偏离 EIP3156 标准可能会影响可组合性 -
[L-03] 攻击者可以强制发出带有错误 holder 参数的 Redeemed 事件 已确认
[I-01] 没有津贴抢跑缓解措施 信息 已确认
[I-02] TransferHelper 库在执行转账之前不验证 token 代码大小 信息 已确认
[I-03] 函数参数中缺少输入验证 信息 已确认
[I-04] 注释和 NatSpec 文档中存在印刷错误和缺少数据 信息 已修复
[I-05] 函数排序不遵循 Solidity 风格指南 信息 已确认

高严重程度

[H-01] VariableInterestRateOracle:get 函数中的精度损失会影响 Yield Variable Rate 协议的利率

上下文: VariableInterestRateOracle.sol#L200-L204, VariableInterestRateOracle.sol#L206-L212, VariableInterestRateOracle.sol#L194-L196

描述: 代码中使用的利率计算公式基于 AAVE 使用的公式。

如果 utilizationRate <= rateParameters.optimalUsageRate,则利率计算如下:

interestRate = rateParameters.baseVariableBorrowRate +
 utilizationRate.wmul(rateParameters.slope1).wdiv(rateParameters.optimalUsageRate

但是,由于 wmulwdiv 函数一起使用,因此计算执行如下: interestRate = rateParameters + utilizationRate * rateParameters.slope1 / 1e18 * 1e18 / rateParameters.optimalUsageRate

这会导致精度损失,因为在乘法 (* 1e18) 之前 执行除法 (/ 1e18)。

utilizationRate > rateParameters.optimalUsageRate 时,也会发生类似的问题。

建议的缓解措施: 为了避免 get 函数中的精度损失,建议按如下方式调整计算: interestRate = rateParameters.baseVariableBorrowRate + utilizationRate * rateParameters.slope1 / rateParameters.optimalUsageRate;

Yield 团队: 我们现在已经删除了 wmulwdiv 函数的用法。

Christos Pap: 已验证。

[H-02] 不准确的退款逻辑导致底层 Join 合约中的错误会计

上下文: VRLadle.sol#L381, Join.sol#L44

描述: VRLadle 合约中的 repay 函数可供用户用来偿还金库中的所有债务。 根据函数注释,剩余的基础货币将返回给 msg.sender

但是,repay 函数中的退款逻辑存在缺陷。 在减少底层 Join 中的 storedBalance 时,会向用户退款,从而导致 Join 合约中的会计中断,因为当退还剩余资金时,storedBalance 将会越来越小。

攻击者可能会通过向 Join 合约发送大量资金,然后调用 repay 函数来利用此问题,从而导致 storedBalance 显着减少。

Join 合约的 exit 函数:

    /// @dev Transfer `amount` `asset` to `user`
    function exit(address user, uint128 amount) external virtual override auth returns (uint128) {
        return _exit(user, amount);
    }

    /// @dev Transfer `amount` `asset` to `user`
    function _exit(address user, uint128 amount) internal virtual returns (uint128) {
        IERC20 token = IERC20(asset);
        storedBalance -= amount;
        token.safeTransfer(user, amount);
        return amount;
    }

建议的缓解措施: 为了解决此问题,建议向 Join 合约添加一个新函数,该函数检索未入账金额。 应调用此函数而不是 exit 函数,以确保维护正确的会计记录。

Yield 团队: 发现得好。 我认为 Join 需要一个 skim 函数,该函数允许获取实际余额和存储余额之间的差额,类似于

Christos Pap: 已验证。 该问题已通过引入 skim 函数来修复,该函数检索未入账金额

中等严重程度

[M-01] NameSymbolDecimals 将在 VyToken 中具有默认值

上下文: VYToken.sol#L41

描述: yield-utils-v2 ERC20 实现未将 decimalsstringsymbol 声明为不可变的。 由于 VYToken 合约旨在可升级,因此 ERC20 实现的构造函数代码不会在初始化期间执行,从而导致默认值。 因此,VYToken 具有 0 位小数,这与底层 token 不同。

由于基于代理的可升级性系统的要求,在可升级合约中不能使用任何 constructors。 不可变将起作用,因为它们不存储在存储中,并且编译器会将它们放置在部署中的字节码中。

如果我们运行以下代码片段,我们可以看到 VYToken 具有 0 位小数,这与预期行为不同。

  function testDecimals() public {
        console.log("underlying is:", vyToken.underlying());
        console.log(vyToken.decimals());
        console.log("Name is", vyToken.name());
 }

建议的缓解措施: 建议还在 yield-utils-v2 ERC20 token 中将 decimalsstringsymbol 标记为不可变的。 或者,可以使用 initialize 函数 来设置这些值。

Yield 团队: 通过使用 initialize() 函数来设置 decimalsstringsymbol 变量来修复。

Christos Pap: 已验证。

低严重程度

[L-01] 如果现货预言机出现故障,包括清算在内的大部分 Yield Variable Rate Protocol 可能会被冻结

上下文: VRCauldron.sol#L139, ChainlinkMultiOracle.sol#L16

描述: VRCauldron 合约中的 setSpotOracle 函数将 ChainlinkMultiOracle 合约 的实例设置为 IOracle。 但是,ChainlinkMultiOracle 合约 使用单个预言机来获取最新的价格提要。

根据 OpenZeppelin 的 智能合约安全指南 #3:价格预言机的危险

虽然目前没有允许或禁止合约读取价格的白名单机制,但强大的多重签名可以加强这些访问控制。 换句话说,多重签名可以随意立即阻止对价格提要的访问。 因此,为了防止拒绝服务的情况发生,建议使用 Solidity 的 try/catch 结构 以防御性方法查询 ChainLink 价格提要。 这样,如果对价格提要的调用失败,则调用者合约仍然可以控制并可以安全且明确地处理任何错误。

在极端情况下,Chainlink 已将预言机脱机,例如在 UST 崩溃期间,它暂停了 UST/ETH 价格预言机,以防止协议接收到不准确的数据。

建议的缓解措施: 为了防止访问 Chainlink 提要的可能性被拒绝,建议实施一项保护措施,例如备用预言机或可以在需要时采取的替代方法。

Yield 团队: 在极端情况下,可以执行提案以更改 spotOracle。 由于在极端情况下存在风险,我们将坚持使用当前的缓解方法。

Christos Pap: 已确认。

[L-02] 偏离 EIP3156 标准可能会影响可组合性

上下文: VYToken.sol#L248, VYToken.sol#L181-L195

描述: VYTokenEIP3156 标准不完全兼容。

根据 Lender Specification,指出:

在回调之后,flashLoan 函数必须从接收者处获取金额 + 费用 token,或者如果未成功,则恢复。

但是,由于 VYToken 合约中的自定义 _burn 函数,如果合约中有一些资金,则金额从合约中获取。

function _burn(address holder, uint256 principalAmount) internal override returns (bool) {
        // 第一步是使用锁定在此合约中的任何 token
        uint256 available = _balanceOf[address(this)];
        if (available >= principalAmount) {
            return super._burn(address(this), principalAmount);
        } else {
            if (available > 0) super._burn(address(this), available);
            unchecked {
                _decreaseAllowance(holder, principalAmount - available);
            }
            unchecked {
                return super._burn(holder, principalAmount - available);
            }
        }
    }

建议的缓解措施: 为了使 VYToken 合约完全兼容 EIP3156 标准,建议删除 custom _burn 函数并相应地修改 VYToken

Yield 团队: 嗨,你说得对,这与书面标准有所偏差。 但是,当我在编写它时,我打算允许这样做(与 4626 中的相同)。

这意味着如果借款人批准还款,那么它应该始终有效。 但是,如果贷方也有其他还款方式,并且借款人决定使用它,则不应有任何障碍。

感谢你提出这个问题,我将修改 3156 中的措辞,而不是在此处更改代码。

Christos Pap: 已验证。

[L-03] 攻击者可以强制发出带有错误 holder 参数的 Redeemed 事件

上下文: VYToken.sol#L111, VYToken.sol#L143

描述: 通过使用 VyToken 合约中存在的任何 token,custom _burn 函数允许用户将 vyToken 转移到合约以启用 burn,从而可能节省 approvepermit 的成本。

但是,此功能可能会被攻击者利用,因为合约中的 withdrawredeem 函数允许用户将 holder 地址作为参数输入。 攻击者可以将 token 直接发送到合约,然后在 withdraw /redeem 函数中传递任何地址作为 holder 参数,这将强制该地址在 Redeemed 事件中发出。

建议的缓解措施: 在当前设置下,很难缓解此问题。 一种可能的解决方案是在 withdaw/redeem 函数中检查批准。

Yield 团队: 我们的用户被指示严格使用前端。 因此,我们不会对此进行更改。

Christos Pap: 已确认。

信息

[i-01] 没有津贴抢跑缓解措施

上下文: VYToken.sol#L16

描述: VYToken 合约继承自 ERC20Permit,后者又继承自 ERC20 合约。 这些合约是 yield-utils-v2 GitHub 仓库的一部分。

这些合约均未提供针对 allowance front-running attack 的保护。 当 token 所有者授权另一个帐户代表他们转移特定数量的 token 时,可能会发生此攻击。 如果 token 所有者决定更改津贴金额,则消费方可以通过抢跑津贴更改交易来花费所有津贴。

建议 为了缓解此问题,你可以考虑使用 OpenZeppelin ERC20 实现。 此实现包括 [increaseAllowance](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8d633cb7d169f2f8#### [i-03] 函数参数中缺少输入验证 背景: VRCauldron.sol#L114VRCauldron.sol#L149VYToken.sol#L66

描述: Variable Rate Yield 项目的一些函数缺少阈值检查,这可能在某些情况下导致意外行为。

建议的缓解措施: 为了解决这些问题,建议采取以下缓解措施:

  • setDebtLimits 中,你可以添加一个 require 语句,要求 min < maxmin <= max
  • setSpotOracle 函数中,可以添加一个 require 语句,要求 ratio <= 1000000
  • setFlashFeeFactor 函数中,可以添加一个阈值作为 fee 值的上限。

Yield 团队: 我们依靠成熟的治理流程来防止上述问题。

Christos Pap: 已知悉。

[i-04] 注释和 NatSpec 文档中的拼写错误和数据缺失

背景: VRCauldron.sol#L110VRLadle.sol#L220VRLadle.sol#L111VariableInterestRateOracle.sol#L12VYToken.sol#L20VYToken.sol#L21VRLadle.sol#L366

描述: 在审计期间,发现注释和 NatSpec 文档中存在多个拼写错误和数据缺失。请参阅“建议的缓解步骤”部分以获取详细列表。

建议的缓解措施:

  • VRCauldron.sol#L110 中,address -> address 的类型转换是不必要的。
  • VRLadle.sol#L220 中的拼写错误:implemnting 应该为 implementing
  • 由于 addToken 也可以用于移除 token,因此可以考虑将 TokenAdded 事件重命名为 TokenStatusChanged
  • VariableInterestRateOracle.sol 合约缺少 NatSpec 文档
  • Point 事件未在 VYToken 中使用。可以考虑将其删除。
  • 还可以考虑在 FlashFeeFactorSet 事件中也发出旧的 fee 值。
  • repay 函数中的 comment 是错误的。考虑将其替换为:The surplus base will be returned to the refundTo address, if refundTo is different than address(0)(如果 refundTo 与 address(0) 不同,剩余的基础将被返回到 refundTo 地址)。

Yield 团队: 已修复。

Christos Pap: 已验证。

[i-05] 函数排序不符合 Solidity 风格指南

背景: VRWitch.sol#L15VRCauldron.sol#L13VRLadle.sol#L21VYToken.sol#L16

描述: Solidity 中推荐的函数顺序,如 Solidity 风格指南 中所述,如下所示:constructor()receive()fallback()externalpublicinternalprivate。但是,这种排序并没有通过 Variable Rate 代码库强制执行。

建议的缓解措施: 建议遵循 Solidity 风格指南 中概述的 Solidity 中推荐的函数顺序。

Yield 团队: 我们将坚持我们当前的风格。

Christos Pap: 已知悉。

补充说明

  • 在安全审查期间,发现某些代码段不符合 Check Effects Interaction (CEI) 模式。这些函数没有 nonReentrant() modifier 的保护。如果支持任何 ERC777 token,则可能会引入重入(相同函数或跨函数)。 协议团队回应:

    Token 以个案方式支持。到目前为止,我们尚未支持任何 erc777 token,也没有立即计划这样做。如果我们要这样做,我们将在当时调查后果。

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

0 条评论

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