Back to Blog

[The Butterfly Effect] The Compound Security Incident Caused by a Bugfix

Code Auditing
October 10, 2021

By BlockSec Team (@BlockSecTeam)

In the last week, the Compound protocol has a bug that will accidentally send a large number of COMP tokens to users. The cause of this bug (bug 2 in this blog) is due to the incorrect fix of another bug (Bug 1 in this blog) that was previously discovered.

In this blog, we will elaborate the root cause of the first bug and the reason why the fix to the first bug causes the second bug.

Background

The Compound protocol is based on the Compound Whitepaper. Through the cToken contracts, accounts on the blockchain supply capital (Ether or ERC-20 tokens) to receive cTokens or borrow assets from the protocol (holding other assets as collateral). The Compound cToken contracts track these balances and algorithmically set interest rates for borrowers.

To incentive users, users who provide liquidity to Compound (supplying capital) can receive the interest. Specifically, users provide assets (e.g., Ether or other ERC20 tokens) to Compound and receive the corresponding cTokens. When the cToken is returned back to Compound, the underlying assets (Ether or ERC20 tokens) and the interests will be returned to the user, if the user does not have any debt in Compound. For instance, if a user has 1000 Ether, then he/she can put the asset into Compound through cEth.mint(1000) to obtain the cToken.

The cToken represents the underlying assets that have been locked in Compound. The user can further use the cToken as the collateral to borrow other assets. For instance, a user can deposit 1000 Ether through ceth.mint(1000) and then use the obtained cTokens to borrow x Dai worth 75 Ether (over-collateralization -- this number depends on the collateral factor) through cDai.borrow(x).

The core logic is implemented in the Comptroller contract. It maintains the states of a user, e.g., how many tokens have been deposited into Compound by the user, how many tokens have been borrowed by the user, and whether the user can borrow more tokens. The functions invoked in this process include getHypotheticalAccountLiquidityInternal(), borrowAllowed(), mintAllowed(), and etc.

The Compound also has the governance token called COMP. The COMP token can be used to vote for proposals. Besides, the COMP token can be traded in exchanges. Currently, the price of the COMP is around $300.

Bug 1

On September 31, 2021, there was a new proposal (Proposals 62) in the Compound DAO, which aims to fix a bug in the Comptroller.

The bug is related to CompSpeed, which represents the number of COMP tokens that can be distributed to users in each block.

The Flow of the mint Function

In the following, we will use the mint function to describe the cause of this bug. The invocation chain of the mint function is: mintmintInternalmintFresh.

In the function mintFresh, it invokes the mintAllowed and then updates the user's balance of the cToken.

In the function mintAllowed, it first invokes updateCompSupplyIndex and then distributeSupplierComp to 1) update the compSupplyState of the market and 2) distribute the COMP tokens to users.

updateCompSupplyIndex

The function updateCompSupplyIndex will update the status of each market, mainly the compSupplyState[cToken].

In the CompMarketState structure, it records the block number (block) of this update, and the bonus index (index) that will affect the number of COMP tokens that should be distributed to the users (who hold the cToken.)

What is the bonus index (index) for each token? This is the value accumulated over time (shown in the following formula).

This shows the number of COMP that should be distributed to users (for each cToken the user holds).

distributeSupplierComp

The another function distributeSupplierComp is responsible for recording the number of COMP tokens that should be distributed to the user (supplier) in compAccrued[supplier].

Specifically, it updates the global bonus index in compSupplyState (in updateCompSupplyIndex function). Then in the distributeSupplierComp function, the supplyIndex records the current bonus index, and the supplierIndex shows the last bonus index for the user (supplier). The delta value (supplyIndex - supplierIndex) * the user's cToken balance shows the number of COMP tokens that should be distributed to the user.

The Cause of Bug 1

There is another function setCompSpeed to adjust the supplySpeed of the market (compSpeeds[address[cToken]]).

That’s because if we set the CompSpeed for a market to zero, that means the COMP token will not be distributed to users in that market. So if we want to first disable the distribution of COMP for a market and then re-enable it, we can follow these steps:

  • Step I: Set the CompSpeed[cToken] as zero to disable the distribution of COMP tokens.
  • Step II: Invoke the setCompSpeed function to set CompSpeed[cToken] as a non-zero value.

Step I: For markets that have been disabled for the distribution of COMP tokens in Step I (supplySpeed == 0), the block is not zero, since the block is continuously updated in updateCompSupplyIndex (else if (deltaBlocks > 0)).

Step II: When executing the operation in Step II, the setCompSpeedInternal function will go through the else if (compSpeed != 0) statement (line 1083). Then, in lines 1088 to 1093, there is a check if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) to initialize the index and block for a new market. However, since we are re-enabling the distribution of COMP tokens in an existing market (not a new market), the statements in lines 1090 and 1091 will not be executed to initialize the index and block (since compSupplyState[address(cToken)].block is not zero).

In summary, for a currently disabled market, the index is zero. However, the block is not zero. This means that when we re-enable the disabled market by invoking setCompSpeed to set CompSpeed[cToken] as a non-zero value, the index value will NOT be reinitialized to CompInitialIndex (1e36) (lines 1090 and 1091 are not executed).

The Impact of Bug 1

We further dig into the distributeSupplierComp function that is responsible for distributing the COMP tokens.

The supplierIndex is compInitialIndex. However, the supplyIndex is still zero due to the bug, which will cause an underflow for Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36).

Bug 2: Introduced by the Fix to the Bug 1

To fix the bug, the project owner changes the code logic. Specifically, it immediately initializes the index to compInitialIndex when initializing a new market.

Since the global bonus index (index) has been initialized to compInitialIndex, the user bonus index should also be initialized to this value. Let's take a look at the distributeSupplierComp function.

The if condition on line 1234 cannot be satisfied even if supplierIndex == 0 since the supplyIndex is equal to (not bigger than) compInitialIndex (1e36). This causes that the supplierIndex is NOT properly initialized to compInitialIndex (its value is 0). Then the deltaIndex (supplyIndex - supplierIndex) will be compInitialIndex, instead of zero. The supplierTokens will become a large value if the user's balance of the cToken is not zero.

In summary, if a user happens to perform the mint operation before the fix of the bug 1, then he/she has cTokens and the supplierIndex will become zero (since the COMP token has been distributed). Then after the fix of the bug 1 (which introduces the bug 2), when the user invokes the mint function again, he/she can get a large number of COMP token (1e36*ctoken.balanceOf(user)).

Real World

We show the affected markets in the following:

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

For the user (0xa7b95d2a2d10028cc4450e453151181cbcac74fc), the user gets 4,466.542459954989867175 COMP tokens in this transaction (0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308).

The further debugging of the transaction shows that, due to the bug 2, the deltaIndex is 1e36 and the user happens to have the cToken at that time.

The Fix to the Bug 2

The fix to the bug 2 is simple. It changes the if condition in the distributeSupplierComp function.

Lessons

  • This is a bug caused by the fix of another bug. How to thoroughly review the code changes for high-profile projects is still an open question.
  • The DAO can eliminate the risk of centralization. However, it also makes the response to security incidents a slow process.
  • The high profile DeFi projects can take good security practices in traditional programs, e.g., deploying an efficient fuzzing system with a continuous testing process.

About BlockSec

BlockSec is a pioneering blockchain security company established in 2021 by a group of globally distinguished security experts. The company is committed to enhancing security and usability for the emerging Web3 world in order to facilitate its mass adoption. To this end, BlockSec provides smart contract and EVM chain security auditing services, the Phalcon platform for security development and blocking threats proactively, the MetaSleuth platform for fund tracking and investigation, and MetaSuites extension for web3 builders surfing efficiently in the crypto world.

To date, the company has served over 300 esteemed clients such as MetaMask, Uniswap Foundation, Compound, Forta, and PancakeSwap, and received tens of millions of US dollars in two rounds of financing from preeminent investors, including Matrix Partners, Vitalbridge Capital, and Fenbushi Capital.

Official website: https://blocksec.com/

Official Twitter account: https://twitter.com/BlockSecTeam

Sign up for the latest updates
~$39.5M Lost: Allbridge, Wanchain & More | BlockSec Weekly
Security Audits

~$39.5M Lost: Allbridge, Wanchain & More | BlockSec Weekly

During the week of July 20-26, 2026, 8 notable security incidents resulted in approximately $39.5M in total losses across Solana, Ethereum, BNB Chain, Arbitrum, Zilliqa, and Cardano. The highlighted Allbridge Core incident (~$1.65M) exposed a Solana input validation flaw where the same Pool account was accepted in both swap roles, with analysis reconstructed entirely from the deployed program binary. Other analyzed incidents include Wanchain (~$500K, flawed message encoding in a Cardano bridge validator), Zilliqa (~$400K, flawed nonce generation in a Ledger app since 2019), and Lien Finance (~$542K, flawed validation logic in bond exchange).

~$1.35M Lost: BarnBridge, DeFiTuna | BlockSec Weekly
Security Insights

~$1.35M Lost: BarnBridge, DeFiTuna | BlockSec Weekly

This weekly report covers 2 security incidents from July 13 to July 19, 2026, with approximately $1.35M in total losses on Ethereum and Solana. DeFiTuna, a Solana lending protocol, lost ~$570K because the position health check treated a zero-value position as healthy regardless of outstanding debt; the attacker used controlled swap routing and a separate low-liquidity pool to trigger this defect. BarnBridge lost ~$776K after an attacker exploited the protocol's deprecated but still-active governance system on Ethereum to pass a malicious proposal and drain user-approved USDC.

~$800K Lost: Hinkal Double-Spend | BlockSec Weekly
Security Insights

~$800K Lost: Hinkal Double-Spend | BlockSec Weekly

This weekly security report covers 1 notable incident from June 29 to July 5, 2026, with approximately $800K in total losses on Ethereum. The Hinkal shielded-pool protocol was drained through a double-spend attack that likely exploited a flaw in the legacy note format, allowing the attacker to derive multiple nullifiers from a single deposit. The report analyzes the probable circuit-level vulnerability, the attack flow, and broader implications for nullifier-based privacy protocols.

Best Security Auditor for Web3

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

BlockSec Audit