1inch 固定利率掉期审计

源节点: 1609993

介绍

1inch 团队要求我们审查和审计他们的固定利率掉期智能合约。我们查看了代码,现在发布我们的结果。

范围

我们审核了提交 b1600f61b77b6051388e6fb2cb0be776c5bcf2d11inch/fixed-rate-swap 存储库。范围包括以下合同:

- FixedRateSwap.sol

所有其他项目文件和目录,包括测试、外部依赖项和项目、博弈论和激励设计,均被排除在本次审计的范围之外。假设外部代码和合同依赖项按文档规定工作。

总体健康

我们发现代码库很复杂,通常缺乏足够的文档来解释整个过程中使用的重要数学和逻辑。对于这种规模的代码库,我们认为本报告中包含的问题数量之多表明了不必要的复杂性。由于缺乏文档,代码库并未达到我们期望的生产就绪系统的完善程度。消除连续费用函数(尽管很聪明)并将其替换为三个独立的线性函数可以使大部分代码库更容易推理。更简单的设计可以消除许多已发现问题的最复杂的代码块。强烈建议重构和简化协议,在这种情况下,我们建议对其进行额外的审核。

总而言之,1inch 团队随时准备好解决审核期间的任何问题,并且非常容易合作。他们乐于接受反馈和建议以持续改进。

气体消耗注意事项

代码库中有一些特别消耗 Gas 的部分,例如 _powerHelper 函数和二分搜索,我们观察到在某些边缘情况下使用了超过 400k 的 Gas。考虑到协议的性质及其打算处理的稳定币,项目的复杂性导致的高天然气消耗可能会破坏协议的目的,并且肯定会影响其总体可用性。

系统总览

固定利率掉期协议是一种自动做市商(AMM),它使用恒定总价格曲线来促进价格彼此稳定的资产之间的掉期。
它还实施了可变费用机制 通常 收取0.01%的费用。当 AMM 的代币余额达到极端条件时,费用要么降低至 0%,要么提高至 0.2%,具体取决于该活动是否适合池子的资产构成。此类费用保存在资金池中,流动性提供者将通过持有股份(AMM 自己的股票)从中受益。 ERC20 LP 代币)。

特权角色

协议的审核版本中没有特权角色。

外部依赖和信任假设

该协议并不打算支持 ERC20 收取转账费用的代币。

此外,值得一提的是,有些资产可能会产生与本报告中描述的类似的意外行为,具体取决于其性质,例如彼此之间小数位数不一致的稳定币或可能允许无限制闪电贷款的资产。

发现

在这里,我们展示我们的发现。

更新: 1inch 团队根据我们的建议应用了多项修复,并向我们提供了一组针对所发现的相应问题的提交。这些提交在每个各自的问题上都有说明,并且我们解决了每个单独提交下引入的修复程序。但是,我们仅审查了我们报告的问题的特定补丁。代码库经历了一些我们尚未审核的其他更改,我们建议在未来的审核中深入审查这些更改。

严重程度

没有。

高严重性

没有。

中等严重程度

[M01]缺乏产出保障可能导致资金损失

FixedRateSwap 合同有利于 交换 一种稳定币兑换另一种稳定币,还允许用户 定金退出 流动性池“份额”的资产(LP 代币)。

然而,掉期、提款和存款都没有为用户提供设置与矿池交互的最低可接受回报值的机制。

该池根据交互如何影响池的组成来评估可变费用。然而,在这种设计中,由于评估费用将直接支付给池股东,因此多数股东有经济动机提前进行大型代币互换,操纵池组成以强制收取更高的费用。

同样,抢先交易也可用于利用代币输出计算中的协议舍入。尽管考虑到流行稳定币生态系统的当前状态,这种可能性不大,但如果 fromBalance 池中代币的数量可以被操纵为以下顺序: 1e36 * inputAmount 对于任何给定的交换,那么 这条线_getReturn 函数将被截断为 zero,导致零值 outputAmount.

但是,因为 零价值掉期受到保护, 内截断 outputAmount 计算将在最坏情况输入之前进行。具体来说,一个 fromBalance [ 范围内的任意位置1e261e35] * the input amount 可能会导致截断和减少 outputAmount 这可能会以牺牲用户的利益为代价,为矿池股东带来利润。

尽管目前这种情况不太可能发生,但未来的生态系统变化和无限制的闪电贷款可能会使这种情况成为更可行的攻击媒介。

为了避免无意的资金损失并减轻可能的抢先交易攻击,请考虑允许用户为与具有货币影响的协议进行任何交互指定可接受的最低回报值。

更新: 固定在 承诺 ea75a86。但是,没有添加特定测试来验证修复实施是否可以防止这种攻击。

严重程度低

[L01] 存款可以在下溢时恢复

如果资金池严重不平衡,存款将在计算期间恢复 shift 在变量 _getVirtualAmountsForDepositImpl 功能 除非存入的总金额大于池中现有余额的1/1000。

例如,尝试在现有余额为 10 的池中存入总计少于 10,000 的代币 token0 和.0001 token1,将恢复。存入更多金额就会成功。

这可以用来创建障碍,阻止其他用户加入池。在这种情况下,仍然可以通过矿池提现和交换代币。也可以按照与池子完全相同的比例进行存款。如果资金池变得不那么单边,那么较小的存款就再次成为可能。

为了避免潜在的拒绝服务攻击,请考虑修改代码库以更好地适应这些边缘情况。至少,考虑显式检查这些条件并在恢复时返回有用的错误消息。

更新: 固定在 承诺 4aa5210.

[L02] 不完整的事件排放

Swap 每次协议促进代币交换时都会发出事件。

但是,有几种公共方法可用于执行代币交换。这 swap0To1swap1To0 函数将交换结果直接发送到 msg.sender,但 swap0To1Forswap1To0For 函数将交换结果发送到由 to 参数。

由于在这两种情况下都会发出相同的事件,链下各方无法区分执行了哪种类型的交换,或者交换的结果传递给了谁。

同样的, DepositWithdrawal 事件只接受一个 user 地址参数,即使它们也在函数中发出,其中 msg.sender 可能与接收令牌的一方不同。

考虑添加索引 recipient 事件的地址参数,以便它们可以更全面地传达协议正在采取的操作。

更新: 已确认,不会修复。

[L03]缺乏输入验证

public getReturn 函数缺少一些输入验证。具体来说:

  • 它并不能验证 tokenFromtokenTo 地址是构成池的代币。
  • 它并不能验证 inputAmount 参数非零。
  • 它并不能验证 fromBalance + toBalance 值非零。

此外,无论是 withdrawFor 也不是 withdrawForWithRatio 函数计算用户是否有足够的股份来提取其请求的金额。无论哪种情况,如果不满足条件,提款都会恢复,但错误消息不明确,只有在消耗了不必要的gas后才会发出。

最后, withdrawForWithRatio 函数忽略确保用于虚拟余额计算的值小于或等于合约的实际余额。这种情况会导致神秘的算术恢复 计算时 balanceXbalanceY 等加工。为 _getRealAmountsForWithdrawImpl 功能.

缺乏对用户控制参数的验证可能会导致难以调试的错误或失败事务。为了避免这种情况,请考虑提高错误消息的清晰度并添加输入验证来解决上述问题。

更新: 部分固定在 承诺 63b6a95承诺 7c0ade7。仅当以下情况时才被验证 inputAmount 价值为零,并且在燃烧 LP 代币以降低 Gas 成本时,操作顺序已被移至早期失败。 1inch团队对此问题的声明:

tokenFrom 和 tokenTo 验证不是强制性的,因为在交换方法中它们是从常量替换的

[L04]未声明或明确命名的常量

FixedRateSwap 合同,字面值 998, 10001002,用于逐步执行费用 _getRealAmountsForWithdrawImpl_getVirtualAmountsForDepositImpl 函数,没有附带的解释或内联文档。

此外,缺乏内联文档也会影响 命名不明确的常量 它们用于整个代码库的计算。

为了提高代码的可读性并促进重构,请考虑为每个“神奇值”定义一个常量,并确保所有常量都有清晰且不言自明的名称。对于复杂值,请考虑添加内联文档来解释它们的计算方式或选择它们的原因。

更新: 固定在 承诺 7091076.

[L05] 误导性或不完整的内联文档

在整个代码库中,发现了一些误导性和/或不完整的内联文档实例,应予以修复。

以下是不完整的内联文档的实例:

以下是误导性内联文档的实例:

  • 计算 私有函数的 _powerHelper 没有明确指出乘法的结果除以 1e18 以防止值的双重缩放。此外,缩放因子的指数也与函数给出的幂相匹配,如果不加注意,可能会产生额外的混乱。

清晰的内联文档对于概述代码的意图至关重要。内联文档和实现之间的不匹配可能会导致对系统预期行为的严重误解。考虑修复任何错误并在发现的地方添加额外的文档,以避免开发人员、用户和审计人员感到困惑。

更新: 已确认,不会修复。

[L06] 没有可访问的覆盖率报告

虽然 README 文件 指向覆盖率报告,是无法访问的。

此外,没有运行测试覆盖脚本的说明。

考虑使覆盖率报告易于访问并明确记录如何运行测试覆盖率脚本。

更新: 部分固定。现在可以访问覆盖范围报告,但是 README 文件仍然没有解释如何运行脚本。

[L07] 潜在不安全的未经检查的数学

整个 FixedRateSwap 合同有很多用途 unchecked 数学。使用的主要原因 unchecked math 的目的是在依赖此类行为或已知不会下溢/上溢的情况下删除上溢/下溢检查。这样做的好处是节省gas,但如果使用不当,可能会导致意想不到的结果和潜在的漏洞。

实例 unchecked 确定了可能下溢/上溢的算术。例如:

溢出这些计算所需的必要值,与整个代码库中的合理验证相结合,通常可以防止这些溢出在实践中实现,但它们仍然是 理论上 可能的。考虑不使用 unchecked 数学除非溢出的可能性是 完全 排除,以防止意外结果并减少协议的整体攻击面。

更新: 已确认,不会修复。

[L08] 整数的不安全显式转换

Swap 事件需要一个 addressint256 参数并在中发出 swap0To1, swap1To0, swap0To1Forswap1To0For 功能。

然而,在发射期间,传递给事件的整数值是从 uint256int256 值。

尽管在今天的实践中不太可能出现问题,但生态系统的发展(例如稳定币资产的无限制闪电贷)可能会导致这种设计在未来表现出不良行为。在给定的足够大的情况下 uint256 值,显式转换为 int256 类型会截断其值。因此,依赖于事件发射的准确性的链下系统可能会被误导。

考虑重新定义 Swap 直接处理的事件 uint256 值,以便发出事件的函数可以放弃显式强制转换。

更新: 固定在 承诺 8436c6c。 1英寸团队 导入 OpenZeppelin 的 SafeCast 图书馆 安全地施放上述案例。

[L09]提款过程可能会导致地板损坏

当股东使用 withdraw 或者 withdrawFor 功能、契约 计算资产数额 他们有权获得 股份数量。然后这些资产将转移给指定的接收者。

但是,由于 if 正在使用语句 而非 require 声明来验证是否应将任何资产发送给接收者,如果资产池不平衡且份额数量较小,则合约可以降低其中一项或两项资产发送的价值。

鉴于涉及的值必然非常小,并且考虑到协议无法完全限制提款(其中一个代币输出实际上可能为零),请考虑记录这种潜在的舍入行为,以便用户在提款时意识到这一点。

更新: 已确认,不会修复。

注释和其他信息

[N01] 已弃用的项目依赖项

在安装过程中 项目的依赖关系,NPM 警告已安装其中一个软件包, Highlight,“将来将不再受支持或接收安全更新”。

尽管此包不太可能导致安全风险,但请考虑将使用此包的依赖项升级到维护版本。

更新: 固定在 承诺 0a2b55d。但是,安装现在需要使用 -force 标记节点的当前 LTS 版本以便成功。

[N02] 资金池费用可能会刺激存款不平衡

存入时 FixedRateSwap 合同 _getVirtualAmountsForDeposit 函数计算存款的虚拟价值。虚拟金额是根据矿池当前资产比例收取费用后缩放的原始金额。

如果用户在以下地址存入资金 池内资产流动比率 那么他们就不会被收取费用。否则,将根据其存款比率与矿池流动资产比率之间的差额收取费用。

这种设计意味着,当池内资产比例不平衡时,用户将被激励以相同的不平衡比例进行存款,而不是按照使池内资产更加平衡的比例进行存款。

为了鼓励资金池更加平衡,请考虑激励平衡资金池的存款,而不是惩罚它们。或者,如果这不可行,请考虑在项目文档中解释原因。

更新: 已确认,不会修复。

[N03]未记录的隐含批准要求

FixedRateSwap 合同隐含地假定在执行之前已获得适当的津贴 掉期存款 这必然会转移代币。

为了支持明确性并提高代码库的整体清晰度,请考虑在相关功能的内联文档中记录所有批准要求。

更新: 已确认,不会修复。

[N04] 令人困惑的隐式验证 outputAmount

getReturn 函数提供了三个参数,即要交换的令牌(tokenFrom),要交换到 (tokenTo)和输入量(inputAmount 的价值 tokenFrom 资产)。相应的 outputAmount 值(结果金额 tokenTo 资产)则为 计算并返回.

在计算之前,函数 需要inputAmount 值小于或等于该池的代币余额 tokenTo 资产。但是,那 outputAmount 值,可以说是更直观的值来检查 tokenTo 资产余额,从未明确检查相同条件。

事实上, 用于计算的数学 outputAmount 折扣值 确保它将严格小于或等于 inputAmount 计算值。

然而,这种行为的意图尚不清楚,即这种设计是否只是为了在执行过程中更快地失败以降低天然气成本并不明显。考虑明确记录所使用的精确检查的推理。此外,请考虑验证余额是否 to 资产大于 outputAmount 值而不是 inputAmount 计算值。

更新: 固定在 承诺 10f4d9c.

[N05]命名不一致

FixedRateSwap 合同规定了明确的 一对令牌 掉期、取款和存款操作的目的是进行操作。在整个代码库中,这些标记都标有以下索引: 0 or 1,如在 token0token1。以描述性命名来传达有关使用详细信息的函数也使用这些标记索引,例如 swap0To1 功能。

但是,对于 withdrawForWithRatio 函数,定义接收比例的参数 token0token1 被命名 firstTokenShare 这可能会引起混乱,因为它指的是 token1 资产而不是 token0 资产。

为了提高整体可读性并减少潜在的混乱,请考虑在整个代码库中保持命名约定一致。

更新: 固定在 承诺 57ad4cd.

[N06]恢复消息格式不一致

require 构造函数中的语句FixedRateSwap 合同的格式不同于所有其他合同 require 合同中的陈述。

由于格式不一致的恢复消息可能会带来不必要的混乱,因此请考虑确保所有 require 报表已恢复格式一致、准确、信息丰富且用户友好的消息。

更新: 固定在 承诺 0aa4e9d.

[N07]命名返回变量的使用不一致

命名返回变量的使用不一致 FixedRateSwap 合同。

具体来说,虽然大多数函数返回命名变量, decimals, _getVirtualAmountsForDepositImpl, _getRealAmountsForWithdrawImpl_checkVirtualAmountsFormula 函数返回显式值。

考虑采用一致的方法在整个代码库中返回值,方法是删除所有命名的返回变量,将它们显式声明为局部变量,并在适当的情况下添加必要的返回语句。 这将提高代码的明确性和可读性,并且还可能有助于减少未来代码重构期间的回归。

更新: 已确认,不会修复。

[N08] Gas优化

FixedRateSwap 合同中,有机会减少一些简单的天然气消耗。例如:

  • _getVirtualAmountsForDeposit private 函数仅从代码库中的一个位置调用,那就是 depositFor 功能。内 depositFor 函数有调用 token0.balanceOf(address(this))token1.balanceOf(address(this))。然而,这些完全相同的调用是在顶部进行的 _getVirtualAmountsForDeposit 功能。后一个函数可以简单地传递所需的值,而不是每次读取两次余额 depositFor 呼叫。
  • _getRealAmountsForWithdrawImpl private 功能, secondTokenShare 变量定义为 _ONE - firstTokenShare。 上 下一行,当它可以简单地使用 secondTokenShare 变量。

为了降低 Gas 成本并进一步简化代码库,请考虑解决上面提出的实例。

更新: 固定在 承诺 34974ee.

[N09] 函数可见性不正确

withdrawWithRatio 函数不被任何函数内部调用 FixedRateSwap 合同。考虑将可见性设置为 external 而不是 public.

更新: 固定在 承诺 cb852f5.

[N10]对匹配的依赖 decimals 可能有问题

协议 隐式要求池内的两个代币都支持 decimals 方法 才能使水池建设取得成功。事实上,这种方法几乎无处不在,但从技术上讲, ERC20 规范的可选组件,其中明确指出合约“不得期望这些值出现”。目前,任何不支持可选的代币 decimals 方法在协议内不可用。

也许问题更大,作为这些令牌调用的一部分 decimals,协议进一步要求两个令牌返回相同的值。

然而,稳定币代币空间在这方面并不均匀。它由许多令牌组成,这些令牌返回各种不同的值 decimals。例如,虽然 USDTUSDC 报告 6 位小数, 报告 18 位小数。

如果这些是协议的有意限制,请考虑明确地触及它们并通过内联文档提供简短的理由。还可以考虑在构建时为不支持的令牌提供更好的错误消息 decimals 方法。或者,考虑使协议更加健壮,以便它可以处理不支持的令牌 decimals 方法或方法对报告不同 decimals 在同一个池内。

更新: 固定在 承诺 b49d808。添加了内联文档。

[N11] 印刷错误

我们在代码库中发现了以下印刷错误:

  • 回复消息已开启 行92 of FixedRateSwap.sol 开头没有大写字母,使其与代码的其余部分不一致。

为了提高代码库的整体一致性和可读性,请考虑纠正此错误以及整个代码库中的任何其他拼写错误。

更新: 固定在 承诺 a92d16a.

[N12] 不必要 virtual 功能

FixedRateSwap 合约继承自 OpenZeppelin 的 ERC20 合同但是它 覆盖它的 ERC20.decimals 功能。这是必需的,因为 FixedRateSwap的流动性池代币,依赖于 decimals 构成池的资产的数量必然是动态的。

然而,尽管该函数的最重要实现应该是最终的,但它是 定义为 virtual 关键词,表明它不一定是最终实现并允许再次覆盖它。

为了避免混淆并澄清意图,请考虑删除 virtual 关键字或记录保留它的原因。

更新: 固定在 承诺 8bce5ec.

结论

没有发现高严重性问题。提出了一些更改,以遵循最佳实践并减少潜在的攻击面。

时间戳记:

更多来自 打开齐柏林飞艇