Compound’s Self-Liquidation Bug

On December 8, I alerted the Compound team about a bug in its smart contract code that would allow a malicious borrower to steal funds. That same day, the engineering team flipped a switch on their smart contract to disable new borrowing. Then, a few days later, the team deployed a patch that was reviewed by its initial auditors and allowed borrowing to resume. With the protocol operating smoothly since the fix, I thought I should share a few thoughts on the issue.

How I Found the Bug

The Bug

Compound incentivizes third-party agents, called liquidators, to ensure that the value of assets a borrower has in the money market is at least a certain percentage (currently 150%) of the value they owe from borrowing. If a borrower is below this threshold, a liquidator can take some of the borrower’s collateral at a discounted price and pay down enough of the loan so that the borrower’s position is sufficiently collateralized again.

// Get the borrower's collateral balance
Balance storage borrowBalance_TargeUnderwaterAsset = borrowBalances[targetAccount][assetBorrow];
// Get the liquidator's collateral balance
Balance storage supplyBalance_LiquidatorCollateralAsset = supplyBalances[localResults.liquidator][assetCollateral];
// Subtract from the borrower's collateral
(err, localResults.updatedSupplyBalance_TargetCollateralAsset) = sub(localResults.currentSupplyBalance_TargetCollateralAsset, localResults.seizeSupplyAmount_TargetCollateralAsset);
// Add to the liquidator's collateral
(err, localResults.updatedSupplyBalance_LiquidatorCollateralAsset) = add(localResults.currentSupplyBalance_LiquidatorCollateralAsset, localResults.seizeSupplyAmount_TargetCollateralAsset);
// Save the borrower's new collateral balance
supplyBalance_TargetCollateralAsset.principal = localResults.updatedSupplyBalance_TargetCollateralAsset;
// Save the liquidator's new collateral balance
supplyBalance_LiquidatorCollateralAsset.principal = localResults.updatedSupplyBalance_LiquidatorCollateralAsset;

Implications

The bug would allow a malicious borrower to liquidate their own position and take additional collateral from the protocol.

The Fix

The Compound team deployed a workaround by requiring that all liquidations be funneled through a separate Liquidator contract. The new interest rate model detects whether it is being called in the context of a liquidation and will revert if the liquidation does not originate at the Liquidator contract.

Takeaways

Compound deserves credit for building an ambitious smart contract protocol. For example, Compound is the only lending protocol in which:

  • Multiple assets can be used as collateral for a single position
  • A borrower’s collateral earns interest
  • The Compound team was able to mobilize and deploy a fix within a few hours of the initial bug report on a Saturday. This was seriously impressive.
  • The Compound team awarded a bug bounty. This will encourage future responsible disclosures.
  • Teams should use established smart contract best practices. Compound’s money market would be a lot easier to audit if it simply reverted instead of trying to fail gracefully everywhere.
  • Users should know that there is some degree of centralization in many smart contract protocols like Compound. The team was able to deploy a fix so quickly because it has special controls on the smart contract. The promise of this technology is to open up new markets without requiring trust in any institution.
  • Open financial protocols like Compound’s money market are exciting, but early and experimental. There may still be bugs that result in losses.