Comptroller: compSpeed bug

Note: All funds are safe. No urgent action is required.

This afternoon @TylerEther messaged me that he might have found a bug related to setting a compSpeed for already listed markets with a current speed of 0. @elee and @TylerEther were able to diagnose the bug and provide a fix.

Bug 1:

When attempting to run the scenario that checks adding a compSpeed for cMKR and the mint cMKR we get an integer underflow error for the mint.

This was happening on line 1161, here compound-protocol/Comptroller.sol at master · compound-finance/compound-protocol · GitHub

function distributeSupplierComp(address cToken, address supplier) internal {
	CompMarketState storage supplyState = compSupplyState[cToken];
	Double memory supplyIndex = Double({mantissa: supplyState.index});
	Double memory supplierIndex = Double({mantissa: compSupplierIndex[cToken][supplier]});
	compSupplierIndex[cToken][supplier] = supplyIndex.mantissa;
	if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) {
	supplierIndex.mantissa = compInitialIndex;
	}

	Double memory deltaIndex = sub_(supplyIndex, supplierIndex); <<< RIGHT HERE!!!!
	uint supplierTokens = CToken(cToken).balanceOf(supplier);
	uint supplierDelta = mul_(supplierTokens, deltaIndex);
	uint supplierAccrued = add_(compAccrued[supplier], supplierDelta);
	compAccrued[supplier] = supplierAccrued;
	emit DistributedSupplierComp(CToken(cToken), supplier, supplierDelta, supplyIndex.mantissa);
}

This happened because the supplyIndex was smaller than the supplierIndex.

Why? Looking at the code, supplyIndex is obtained through these two lines.

CompMarketState storage supplyState = compSupplyState[cToken]; <<< RIGHT HERE!!!!

Double memory supplyIndex = Double({mantissa: supplyState.index});

After some looking, we found the issue is supplyState.index was not being set to the correct number because when supplyState/borrowState already has an entry with block information, and the current compSpeed is equal to zero, we do not update the market state.

Here is the function where supplyState is set: compound-protocol/Comptroller.sol at master · compound-finance/compound-protocol · GitHub

function setCompSpeedInternal(CToken cToken, uint compSpeed) internal {
	uint currentCompSpeed = compSpeeds[address(cToken)];
	if (currentCompSpeed != 0) {
		// note that COMP speed could be set to 0 to halt liquidity rewards for a market
		Exp memory borrowIndex = Exp({mantissa: cToken.borrowIndex()});
		updateCompSupplyIndex(address(cToken));
		updateCompBorrowIndex(address(cToken), borrowIndex);
	} else if (compSpeed != 0) {
		// Add the COMP market
		Market storage market = markets[address(cToken)];
		require(market.isListed == true, "comp market is not listed");
		if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) {
	compSupplyState[address(cToken)] = CompMarketState({
			index: compInitialIndex,
			block: safe32(getBlockNumber(), "block number exceeds 32 bits")
		});
	}
	if (compBorrowState[address(cToken)].index == 0 && compBorrowState[address(cToken)].block == 0) {
		compBorrowState[address(cToken)] = CompMarketState({
			index: compInitialIndex,
			block: safe32(getBlockNumber(), "block number exceeds 32 bits")
			});
		}
	}
	if (currentCompSpeed != compSpeed) {
		compSpeeds[address(cToken)] = compSpeed;
		emit CompSpeedUpdated(cToken, compSpeed);
	}
}

Importantly, look at these statements.

if (currentCompSpeed != 0) {
		// note that COMP speed could be set to 0 to halt liquidity rewards for a market
		Exp memory borrowIndex = Exp({mantissa: cToken.borrowIndex()});
		updateCompSupplyIndex(address(cToken));
		updateCompBorrowIndex(address(cToken), borrowIndex);
	}
if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) {
	compSupplyState[address(cToken)] = CompMarketState({
			index: compInitialIndex,
			block: safe32(getBlockNumber(), "block number exceeds 32 bits")
		});
	}

If the block isn’t zero, it won’t set a compMarketState and since currentCompSpeed is 0, it does not update it either.

This can be fixed with the following change (@TylerEther)

if (compSupplyState[address(cToken)].index == 0) {
	compSupplyState[address(cToken)] = CompMarketState({
		index: compInitialIndex,
		block: safe32(getBlockNumber(), "block number exceeds 32 bits")
	});
} else {
	compSupplyState[address(cToken)].block = safe32(getBlockNumber(), "block number exceeds 32 bits");
}

Instead of not setting the entry when block != 0, we instead update the entry.

Bug 2

This patch also fixes another separate but related bug.

Previously, if the compSpeed for a token was set above 0 at block [A], set back to 0 at block [B], then set to positive number R at block [C], since there would have already been an index & block set, the block number would get updated with market activity, but the index would remain at the index of [B].

This meant that at block [C], rewards would be accrued from the block of the last market activity prior to [C] using the rate R.

This was incorrect, as COMP accrued at rate R should only be calculated from when it was set, aka block [C].

As the patch now properly updates the block value of the market state to [C], interest will then be corrected properly, using rate R between blocks [C] and [C]+N.

Relevant Pull Request

5 Likes

Hi @getty, I’m Joyce from TrueUSD. May I know when would the bug be fixed?

Hi Joyce,

We just identified and patched another minor problem with COMP reward distributions which will take some additional time to test and review.

I’m planning to make a proposal on Monday, September 20; to be executed Friday, September 24.

3 Likes