Compound Finance zero-day: prices can move faster than advertised

By William Entriken

3 minutes

SUBJECT: BUG: Automated price oracle can violate guaranteed max swing without human intervention

Summary

The price oracle documentation specifies how price anchors are set. However the implementation does not follow that specification. This makes possible a violation of guarantees made by the system.

Specification

Source: https://github.com/compound-finance/compound-oracle/blob/master/docs/Oracle-Specification.pdf

At the beginning of every hour, the price is stored as a new anchor price, and every price for the rest of the hour must be within 10% of that price.

Implementation

Source: https://github.com/compound-finance/compound-oracle/blob/master/contracts/PriceOracle.sol#L301

localVars.currentPeriod = (block.number / numBlocksPerPeriod) + 1;
uint public constant numBlocksPerPeriod = 240; // approximately 1 hour: 60 seconds/minute * 60 minutes/hour * 1 block/15 seconds

The period is set based on block number.

Details

The implementation assumes 240 blocks per hour. This is incorrect, as admitted in the code. Actual block times are variable and skew is possible. See actual times reported by Etherscan.

Impact

It is possible that price swings as large as 22.2% (1.1/0.9) will occur within a single hour without alarming or requiring human interaction. This exceeds the advertised limit of 10% price swings within one hour requiring human intervention.

SEVERITY: Medium

LIKELIHOOD: HIGH

Likelihood is high because token price movements are volatile and it is likely that a swing greater than ±10% will happen in one hour at some point.

Recommendations

Set reasonable expectations

The advertised specification is not possible. I recommend the following new specification:

Our automated tools (price oracles) gather information from exchanges and set the market value of asset prices within the Compound system. As a quality control mechanism, we require any large price changes to have a human review. It works as follows:

Update the implementation

Remove uint period from https://github.com/compound-finance/compound-oracle/blob/master/contracts/PriceOracle.sol#L301.

Add uint block to record the anchor block.

Rename numBlocksPerPeriod to minimumPriceAnchorEffectiveTime, SetPriceLocalVars and other calculation to implement new specification.

Be transparent

Since we are making guarantees to the participants about pricing, we should provide them with the explicit data they need to understand the system.

Create a new event:

event PriceAnchored(address asset, uint priceMantissa);

Make it simpler

Many variables are named matching New[Pending]Anchor[Admin] this reduces readability.

The entire concept of a pending anchor is unnecessary. Just use:

event PriceAnchoredByAdmin(address asset, uint priceMantissa);

and have it take immediate effect. This will save gas and increases code auditability.

Timeline

Compound has announced version two of the protocol, but the affected version is still live.

Comments

There are no comments yet.

Please discuss this topic anywhere and let me know any great comments or media coverage I should link here.