Skip to main content

Comptroller

Introduction

The Comptroller is the risk management layer of the Compound protocol; it determines how much collateral a user is required to maintain, and whether (and by how much) a user can be liquidated. Each time a user interacts with a xToken, the Comptroller is asked to approve or deny the transaction.

The Comptroller maps user balances to prices (via the Price Oracle) to risk weights (called Collateral Factors) to make its determinations. Users explicitly list which assets they would like included in their risk scoring, by calling Enter Markets and Exit Market.

Architecture

The Comptroller is implemented as an upgradeable proxy. The Unitroller proxies all logic to the Comptroller implementation, but storage values are set on the Unitroller. To call Comptroller functions, use the Comptroller ABI on the Unitroller address.

Enter Markets

Enter into a list of markets - it is not an error to enter the same market more than once. In order to supply collateral or borrow in a market, it must be entered first.

Comptroller

function enterMarkets(address[] calldata xTokens) returns (uint[] memory)

  1. msg.sender: The account which shall enter the given markets.
  2. xTokens: The addresses of the xToken markets to enter.
  3. RETURN: For each market, returns an error code indicating whether or not it was entered. Each is 0 on success, otherwise an Error code.

Solidity


Comptroller troll = Comptroller(0xABCD...);

xToken[] memory xTokens = new xToken[](2);

xTokens[0] = XErc20(0x3FDA...);

xTokens[1] = XEther(0x3FDB...);

uint[] memory errors = troll.enterMarkets(xTokens);

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const xTokens = [XErc20.at(0x3FDA...), XEther.at(0x3FDB...)];

const errors = await troll.methods.enterMarkets(xTokens).send({from: ...});

Exit Market

Exit a market - it is not an error to exit a market which is not currently entered. Exited markets will not count towards account liquidity calculations.

Comptroller

function exitMarket(address xToken) returns (uint)

  1. msg.sender: The account which shall exit the given market.
  2. xTokens: The addresses of the xToken market to exit.
  3. RETURN: 0 on success, otherwise an Error code.

Solidity


Comptroller troll = Comptroller(0xABCD...);

uint error = troll.exitMarket(xToken(0x3FDA...));

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const errors = await troll.methods.exitMarket(XEther.at(0x3FDB...)).send({from: ...});

Get Assets In

Get the list of markets an account is currently entered into. In order to supply collateral or borrow in a market, it must be entered first. Entered markets count towards account liquidity calculations.

Comptroller

function getAssetsIn(address account) view returns (address[] memory)

  1. account: The account whose list of entered markets shall be queried.
  2. RETURN: The address of each market which is currently entered into.

Solidity


Comptroller troll = Comptroller(0xABCD...);

address[] memory markets = troll.getAssetsIn(0xMyAccount);

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const markets = await troll.methods.getAssetsIn(xTokens).call();

Collateral Factor

A xToken’s collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by minting the xToken. Generally, large or liquid assets have high collateral factors, while small or illiquid assets have low collateral factors. If an asset has a 0% collateral factor, it can’t be used as collateral (or seized in liquidation), though it can still be borrowed.

Collateral factors can be increased (or decreased) through Compound Governance, as market conditions change.

Comptroller

function markets(address xTokenAddress) view returns (bool, uint, bool)

  1. xTokenAddress: The address of the xToken to check if listed and get the collateral factor for.
  2. RETURN: Tuple of values (isListed, collateralFactorMantissa, isComped); isListed represents whether the comptroller recognizes this xToken; collateralFactorMantissa, scaled by 1e18, is multiplied by a supply balance to determine how much value can be borrowed. The isComped boolean indicates whether or not suppliers and borrowers are distributed COMP tokens.

Solidity


Comptroller troll = Comptroller(0xABCD...);

(bool isListed, uint collateralFactorMantissa, bool isComped) = troll.markets(0x3FDA...);

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const result = await troll.methods.markets(0x3FDA...).call();

const {0: isListed, 1: collateralFactorMantissa, 2: isComped} = result;

Get Account Liquidity

Account Liquidity represents the USD value borrowable by a user, before it reaches liquidation. Users with a shortfall (negative liquidity) are subject to liquidation, and can’t withdraw or borrow assets until Account Liquidity is positive again.

For each market the user has entered into, their supplied balance is multiplied by the market’s collateral factor, and summed; borrow balances are then subtracted, to equal Account Liquidity. Borrowing an asset reduces Account Liquidity for each USD borrowed; withdrawing an asset reduces Account Liquidity by the asset’s collateral factor times each USD withdrawn.

Because the Compound Protocol exclusively uses unsigned integers, Account Liquidity returns either a surplus or shortfall.

Comptroller

function getAccountLiquidity(address account) view returns (uint, uint, uint)

  1. account: The account whose liquidity shall be calculated.
  2. RETURN: Tuple of values (error, liquidity, shortfall). The error shall be 0 on success, otherwise an error code. A non-zero liquidity value indicates the account has available account liquidity. A non-zero shortfall value indicates the account is currently below his/her collateral requirement and is subject to liquidation. At most one of liquidity or shortfall shall be non-zero.

Solidity


Comptroller troll = Comptroller(0xABCD...);

(uint error, uint liquidity, uint shortfall) = troll.getAccountLiquidity(msg.caller);

require(error == 0, "join the Discord");

require(shortfall == 0, "account underwater");

require(liquidity > 0, "account has excess collateral");

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const result = await troll.methods.getAccountLiquidity(0xBorrower).call();

const {0: error, 1: liquidity, 2: shortfall} = result;

Close Factor

The percent, ranging from 0% to 100%, of a liquidatable account’s borrow that can be repaid in a single liquidate transaction. If a user has multiple borrowed assets, the closeFactor applies to any single borrowed asset, not the aggregated value of a user’s outstanding borrowing.

Comptroller

function closeFactorMantissa() view returns (uint)

  1. RETURN: The closeFactor, scaled by 1e18, is multiplied by an outstanding borrow balance to determine how much could be closed.

Solidity


Comptroller troll = Comptroller(0xABCD...);

uint closeFactor = troll.closeFactorMantissa();

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const closeFactor = await troll.methods.closeFactorMantissa().call();

Liquidation Incentive

The additional collateral given to liquidators as an incentive to perform liquidation of underwater accounts. A portion of this is given to the collateral xToken reserves as determined by the seize share. The seize share is assumed to be 0 if the xToken does not have a protocolSeizeShareMantissa constant. For example, if the liquidation incentive is 1.08, and the collateral’s seize share is 1.028, liquidators receive an extra 5.2% of the borrower’s collateral for every unit they close, and the remaining 2.8% is added to the xToken’s reserves.

Comptroller

function liquidationIncentiveMantissa() view returns (uint)

  1. RETURN: The liquidationIncentive, scaled by 1e18, is multiplied by the closed borrow amount from the liquidator to determine how much collateral can be seized.

Solidity


Comptroller troll = Comptroller(0xABCD...);

uint closeFactor = troll.liquidationIncentiveMantissa();

Web3 1.0


const troll = Comptroller.at(0xABCD...);

const closeFactor = await troll.methods.liquidationIncentiveMantissa().call();

Key Events

EventDescription
MarketEntered(xToken xToken, address account)Emitted upon a successful Enter Market.
MarketExited(xToken xToken, address account)Emitted upon a successful Exit Market.

Error Codes

CodeNameDescription
0NO_ERRORNot a failure.
1UNAUTHORIZEDThe sender is not authorized to perform this action.
2COMPTROLLER_MISMATCHLiquidation cannot be performed in markets with different comptrollers.
3INSUFFICIENT_SHORTFALLThe account does not have sufficient shortfall to perform this action.
4INSUFFICIENT_LIQUIDITYThe account does not have sufficient liquidity to perform this action.
5INVALID_CLOSE_FACTORThe close factor is not valid.
6INVALID_COLLATERAL_FACTORThe collateral factor is not valid.
7INVALID_LIQUIDATION_INCENTIVEThe liquidation incentive is invalid.
8MARKET_NOT_ENTEREDThe market has not been entered by the account.
9MARKET_NOT_LISTEDThe market is not currently listed by the comptroller.
10MARKET_ALREADY_LISTEDAn admin tried to list the same market more than once.
11MATH_ERRORA math calculation error occurred.
12NONZERO_BORROW_BALANCEThe action cannot be performed since the account carries a borrow balance.
13PRICE_ERRORThe comptroller could not obtain a required price of an asset.
14REJECTIONThe comptroller rejects the action requested by the market.
15SNAPSHOT_ERRORThe comptroller could not get the account borrows and exchange rate from the market.
16TOO_MANY_ASSETSAttempted to enter more markets than are currently supported.
17TOO_MUCH_REPAYAttempted to repay more than is allowed by the protocol.

Failure Info

CodeName
0ACCEPT_ADMIN_PENDING_ADMIN_CHECK
1ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK
2EXIT_MARKET_BALANCE_OWED
3EXIT_MARKET_REJECTION
4SET_CLOSE_FACTOR_OWNER_CHECK
5SET_CLOSE_FACTOR_VALIDATION
6SET_COLLATERAL_FACTOR_OWNER_CHECK
7SET_COLLATERAL_FACTOR_NO_EXISTS
8SET_COLLATERAL_FACTOR_VALIDATION
9SET_COLLATERAL_FACTOR_WITHOUT_PRICE
10SET_IMPLEMENTATION_OWNER_CHECK
11SET_LIQUIDATION_INCENTIVE_OWNER_CHECK
12SET_LIQUIDATION_INCENTIVE_VALIDATION
13SET_MAX_ASSETS_OWNER_CHECK
14SET_PENDING_ADMIN_OWNER_CHECK
15SET_PENDING_IMPLEMENTATION_OWNER_CHECK
16SET_PRICE_ORACLE_OWNER_CHECK
17SUPPORT_MARKET_EXISTS
18SUPPORT_MARKET_OWNER_CHECK

COMP Distribution Speeds

COMP Speed

The “COMP speed” unique to each market is an unsigned integer that specifies the amount of COMP that is distributed, per block, to suppliers and borrowers in each market. This number can be changed for individual markets by calling the _setCompSpeed method through a successful Compound Governance proposal. The following is the formula for calculating the rate that COMP is distributed to each supported market.

utility = xTokenTotalBorrows * assetPrice

utilityFraction = utility / sumOfAllCOMPedMarketUtilities

marketCompSpeed = compRate * utilityFraction

COMP Distributed Per Block (All Markets)

The Comptroller contract’s compRate is an unsigned integer that indicates the rate at which the protocol distributes COMP to markets’ suppliers or borrowers, every Ethereum block. The value is the amount of COMP (in wei), per block, allocated for the markets. Note that not every market has COMP distributed to its participants (see Market Metadata). The compRate indicates how much COMP goes to the suppliers or borrowers, so doubling this number shows how much COMP goes to all suppliers and borrowers combined. The code examples implement reading the amount of COMP distributed, per Ethereum block, to all markets.

Comptroller

uint public compRate;

Solidity


Comptroller troll = Comptroller(0xABCD...);

// COMP issued per block to suppliers OR borrowers _ (1 _ 10 ^ 18)

uint compRate = troll.compRate();

// Approximate COMP issued per day to suppliers OR borrowers _ (1 _ 10 ^ 18)

uint compRatePerDay = compRate _ 4 _ 60 \* 24;

// Approximate COMP issued per day to suppliers AND borrowers _ (1 _ 10 ^ 18)

uint compRatePerDayTotal = compRatePerDay \* 2;

Web3 1.2.6

const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);

let compRate = await comptroller.methods.compRate().call();

compRate = compRate / 1e18;

// COMP issued to suppliers OR borrowers

const compRatePerDay = compRate _ 4 _ 60 \* 24;

// COMP issued to suppliers AND borrowers

const compRatePerDayTotal = compRatePerDay \* 2;

COMP Distributed Per Block (Single Market)

The Comptroller contract has a mapping called compSpeeds. It maps xToken addresses to an integer of each market’s COMP distribution per Ethereum block. The integer indicates the rate at which the protocol distributes COMP to markets’ suppliers or borrowers. The value is the amount of COMP (in wei), per block, allocated for the market. Note that not every market has COMP distributed to its participants (see Market Metadata). The speed indicates how much COMP goes to the suppliers or the borrowers, so doubling this number shows how much COMP goes to market suppliers and borrowers combined. The code examples implement reading the amount of COMP distributed, per Ethereum block, to a single market.

Comptroller

mapping(address => uint) public compSpeeds;

Solidity


Comptroller troll = Comptroller(0x123...);

address xToken = 0xabc...;

// COMP issued per block to suppliers OR borrowers _ (1 _ 10 ^ 18)

uint compSpeed = troll.compSpeeds(xToken);

// Approximate COMP issued per day to suppliers OR borrowers _ (1 _ 10 ^ 18)

uint compSpeedPerDay = compSpeed _ 4 _ 60 \* 24;

// Approximate COMP issued per day to suppliers AND borrowers _ (1 _ 10 ^ 18)

uint compSpeedPerDayTotal = compSpeedPerDay \* 2;

Web3 1.2.6

const xTokenAddress = '0xabc...';

const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);

let compSpeed = await comptroller.methods.compSpeeds(xTokenAddress).call();

compSpeed = compSpeed / 1e18;

// COMP issued to suppliers OR borrowers

const compSpeedPerDay = compSpeed _ 4 _ 60 \* 24;

// COMP issued to suppliers AND borrowers

const compSpeedPerDayTotal = compSpeedPerDay \* 2;

Claim COMP

Every Compound user accrues COMP for each block they are supplying to or borrowing from the protocol. Users may call the Comptroller’s claimComp method at any time to transfer COMP accrued to their address.

Comptroller

// Claim all the COMP accrued by holder in all markets

function claimComp(address holder) public

// Claim all the COMP accrued by holder in specific markets

function claimComp(address holder, xToken[] memory xTokens) public

// Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows

function claimComp(address[] memory holders, xToken[] memory xTokens, bool borrowers, bool suppliers) public

Solidity


Comptroller troll = Comptroller(0xABCD...);

troll.claimComp(0x1234...);

Web3 1.2.6

const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);

await comptroller.methods.claimComp("0x1234...").send({ from: sender });

Market Metadata

The Comptroller contract has an array called getAllMarkets that contains the addresses of each xToken contract. Each address in the getAllMarkets array can be used to fetch a metadata struct in the Comptroller’s markets constant. See the Comptroller Storage contract for the Market struct definition.

Comptroller

xToken[] public getAllMarkets;

Solidity


Comptroller troll = Comptroller(0xABCD...);

xToken xTokens[] = troll.getAllMarkets();

Web3 1.2.6

const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);

const xTokens = await comptroller.methods.getAllMarkets().call();

const xToken = xTokens[0]; // address of a xToken