Back to Blog

Bug修复引发的蝴蝶效应:复合安全事件

Code Auditing
October 10, 2021
8 min read

由 BlockSec 团队 (@BlockSecTeam)

在过去的一周,Compound协议出现了一个漏洞,该漏洞会“意外”地向用户发送大量 COMP 代币。该漏洞(本文中的 bug 2)的根本原因是之前发现的另一个漏洞(本文中的 bug 1)被错误地修复了。

在本文中,我们将详细阐述第一个 bug 的根本原因以及第一个 bug 的修复为何会导致第二个 bug。

背景

Compound协议基于 Compound 白皮书。通过 cToken 合约,链上账户向协议提供资金(Ether 或 ERC-20 代币)以接收 cTokens,或者通过持有其他资产作为抵押品来借入资产。 Compound cToken 合约跟踪这些余额并以算法方式设定借款人的利率

为了激励用户,向 Compound 提供流动性(提供资金)的用户可以获得利息。具体来说,用户向 Compound 提供资产(例如 Ether 或其他 ERC20 代币),然后获得相应的 cTokens。当 cToken 被退还给 Compound 时,如果用户在 Compound 中没有任何债务,则底层资产(Ether 或 ERC20 代币)和利息将被退还给用户。例如,如果一个用户拥有 1000 Ether,那么他/她可以通过 cEth.mint(1000) 将资产存入 Compound 来获得 cToken。

cToken 代表已锁定在 Compound 中的底层资产。用户可以进一步将 cToken 用作抵押品来借入其他资产。例如,用户可以通过 ceth.mint(1000) 存入 1000 Ether,然后使用获得的 cTokens 来借入价值 75 Ether 的 x Dai(超额抵押——此数量取决于抵押因子),具体通过 cDai.borrow(x) 进行。

核心逻辑实现在 Comptroller 合约中。它维护用户的状态,例如用户向 Compound 存入了多少代币,用户借入了多少代币,以及用户是否可以借入更多代币。此过程中调用的函数包括 getHypotheticalAccountLiquidityInternal()borrowAllowed()mintAllowed() 等。

Compound 还有一个名为 COMP 的治理代币。 COMP 代币可用于投票赞成提案。此外,COMP 代币可以在交易所交易。目前,COMP 的价格约为 300 美元。

Bug 1

2021 年 9 月 31 日,Compound DAO 中出现了一个新提案(提案 62),旨在修复 Comptroller 中的一个 bug。

该 bug 与 CompSpeed 相关,它代表每个区块可以分配给用户的 COMP 代币数量。

mint 函数的流程

接下来,我们将使用 mint 函数来描述此 bug 的原因。 mint 函数的调用链是:mintmintInternalmintFresh

mintFresh 函数中,它调用 mintAllowed,然后更新用户的 cToken 余额。

mintAllowed 函数中,它首先调用 updateCompSupplyIndex,然后调用 distributeSupplierComp 来 1) 更新市场的 compSupplyState,并 2) 将 COMP 代币分配给用户。

updateCompSupplyIndex

updateCompSupplyIndex 函数将更新每个市场的状态,主要是 compSupplyState[cToken]

CompMarketState 结构体中,它记录了此更新的区块号 (block) 和将影响应分配给用户(持有 cToken 的用户)的 COMP 代币数量的奖励指数 (index)。

每个代币的奖励指数 (index) 是什么? 这是随时间累积的值(如下公式所示)。

这显示了应分配给用户的 COMP 数量(针对用户持有的每个 cToken)。

distributeSupplierComp

另一个函数 distributeSupplierComp 负责记录应分配给用户(供应商)的 COMP 代币数量,存储在 compAccrued[supplier] 中。

具体来说,它在 updateCompSupplyIndex 函数中更新全局奖励指数 compSupplyState。然后在 distributeSupplierComp 函数中,supplyIndex 记录当前的奖励指数,而 supplierIndex 显示用户(供应商)的最后一个奖励指数。差值 (supplyIndex - supplierIndex) 乘以 用户的 cToken 余额,就是应分配给用户的 COMP 代币数量。

Bug 1 的原因

还有一个函数 setCompSpeed 用于调整市场的 supplySpeedcompSpeeds[address[cToken]])。

这是因为如果我们将某个市场的 CompSpeed 设置为零,则意味着 COMP 代币不会分配给该市场的用户。因此,如果我们想先禁用某个市场的 COMP 分配,然后再重新启用它,可以遵循以下步骤:

  • 步骤 I:将 CompSpeed[cToken] 设置为零,以禁用 COMP 代币的分配。
  • 步骤 II:调用 setCompSpeed 函数将 CompSpeed[cToken] 设置为非零值。

步骤 I:对于在步骤 I 中已禁用 COMP 代币分配的市场(supplySpeed == 0),区块号不为零,因为在 updateCompSupplyIndex 中(else if (deltaBlocks > 0))区块号会持续更新。

步骤 II:在执行步骤 II 中的操作时,setCompSpeedInternal 函数将通过 else if (compSpeed != 0) 语句(第 1083 行)。然后,在第 1088 至 1093 行,有一个检查 if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) 来初始化新市场indexblock。然而,由于我们是在现有市场(而非新市场)重新启用 COMP 代币的分配,因此第 1090 和 1091 行的语句不会被执行来初始化 indexblock(因为 compSupplyState[address(cToken)].block 不为零)。

总之,对于当前已禁用的市场,index 为零。但是,block 不为零。这意味着当我们在调用 setCompSpeedCompSpeed[cToken] 设置为非零值来重新启用已禁用的市场时,index不会重新初始化为 CompInitialIndex(1e36)(第 1090 和 1091 行未执行)。

Bug 1 的影响

我们进一步深入研究负责分发 COMP 代币的 distributeSupplierComp 函数。

supplierIndexcompInitialIndex。然而,由于 bug,supplyIndex 仍然为零,这将导致 Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) 发生下溢。

Bug 2:由 Bug 1 的修复引入

为了修复 bug,项目方更改了代码逻辑。具体来说,它在初始化新市场时立即将 index 初始化为 compInitialIndex

由于全局奖励指数 (index) 已初始化为 compInitialIndex,因此用户奖励指数也应初始化为该值。让我们看看 distributeSupplierComp 函数。

即使 supplierIndex == 0,第 1234 行的 if 条件也无法满足,因为 supplyIndex 等于(而非大于)compInitialIndex1e36)。这导致 supplierIndex 未正确初始化为 compInitialIndex(其值为 0)。然后 deltaIndexsupplyIndex - supplierIndex)将是 compInitialIndex,而不是零。如果用户的 cToken 余额不为零,supplierTokens 将变成一个很大的值。

总之,如果用户在 bug 1 被修复之前恰好执行了 mint 操作,那么他/她拥有 cTokens,并且 supplierIndex 将变为零(因为 COMP 代币已被分配)。然后,在 bug 1 被修复之后(引入了 bug 2),当用户再次调用 mint 函数时,他/她将获得大量 COMP 代币(1e36*ctoken.balanceOf(user))。

真实案例

我们在下方展示了受影响的市场:

0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI
0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD
0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR
0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI
0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE
0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI

对于用户(0xa7b95d2a2d10028cc4450e453151181cbcac74fc),在该交易中获得了 4,466.542459954989867175 COMP 代币(0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308)。

对该交易的进一步调试显示,由于 bug 2,deltaIndex 为 1e36,并且用户当时恰好拥有 cToken。

Bug 2 的修复

Bug 2 的修复很简单。它改变了 distributeSupplierComp 函数中的 if 条件。

经验教训

  • 这是一个由另一个 bug 的修复所引起的 bug。如何彻底审查高知名度项目的代码更改仍然是一个悬而未决的问题。
  • DAO 可以消除中心化的风险。然而,它也使得对安全事件的响应过程变得缓慢。
  • 高知名度的 DeFi 项目可以借鉴传统程序的良好安全实践,例如部署一个高效的模糊测试系统并进行持续的测试过程。

关于 BlockSec

BlockSec 是一家开创性的区块链安全公司,由一群全球杰出的安全专家于 2021 年创立。公司致力于为新兴的 Web3 世界增强安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链 安全审计 服务,用于安全开发和主动阻止威胁的 Phalcon 平台,用于资金追踪和调查的 MetaSleuth 平台,以及供 Web3 构建者在加密世界高效冲浪的 MetaSuites 扩展。

迄今为止,公司已为 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap 等 300 多家知名客户提供服务,并在两轮融资中从 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等知名投资者那里获得了数千万美元的投资。

官方网站:https://blocksec.com/

官方 Twitter 账号:https://twitter.com/BlockSecTeam

Sign up for the latest updates
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit