Skip to main content
This case study explains how the Compound upgrade bug worked and which assertion pattern could have constrained the invalid state transition.

What Happened

Compound upgraded their comptroller contract to https://etherscan.io/address/0x374abb8ce19a73f2c4efad642bda76c797f19233 which had a one letter bug on L1217. The bug occurred when users supplied tokens to markets with zero COMP rewards before initialization. In these cases, the supplyIndex equaled compInitialIndex (1e36), but the reward calculation logic was skipped due to the incorrect comparison operator.
The bug was caused by using > instead of >= in the check. This meant that when supplyIndex equaled compInitialIndex (1e36), the if block was skipped, leaving supplierIndex at 0. The large difference between supplierIndex (0) and supplyIndex (1e36) caused the protocol to pay out massive unintended rewards. The previous version worked because supplyIndex started at 0 instead of 1e36, though >= would have been more correct.

Assertion Pattern

It would have made sense for the developers to make sure that the COMPS accrual never exceeds the maximum possible rate. This way even if a bug is introduced, it will be caught by the assertion. The assertion below calculates the maximum possible rate of COMP accrual and checks that a distribution does not exceed this rate.
The code below is conceptual pseudo code to illustrate the invariant that should be enforced. It uses simplified syntax for accessing storage mappings (compound.compAccrued[supplier]) that would not compile in actual Solidity. A real implementation would need to use proper getter functions or storage slot calculations via ph.loadStateAt().