Introduction
The Cointroller is the risk management layer of the Rifi 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 rToken, the Cointroller is asked to approve or deny the transaction.
The Cointroller 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 Cointroller is implemented as an upgradeable proxy. The Unitroller proxies all logic to the Cointroller implementation, but storage values are set on the Unitroller. To call Cointroller functions, use the Cointroller 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.
Cointroller
function enterMarkets(address[] calldata rTokens) returns (uint[] memory)
- msg.sender: The account which shall enter the given markets.
- rTokens: The addresses of the rToken markets to enter.
- 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
Cointroller troll = Cointroller(0xABCD...);RToken[] memory rTokens = new RToken[](2);rTokens[0] = RBep20(0x3FDA...);rTokens[1] = RBinance(0x3FDB...);uint[] memory errors = troll.enterMarkets(rTokens);
Web3 1.0
const troll = Cointroller.at(0xABCD...);const rTokens = [RBep20.at(0x3FDA...), RBinance.at(0x3FDB...)];const errors = await troll.methods.enterMarkets(rTokens).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.
Cointroller
function exitMarket(address rToken) returns (uint)
- msg.sender: The account which shall exit the given market.
- rTokens: The addresses of the rToken market to exit.
- RETURN: 0 on success, otherwise an Error code.
Solidity
Cointroller troll = Cointroller(0xABCD...);uint error = troll.exitMarket(RToken(0x3FDA...));
Web3 1.0
const troll = Cointroller.at(0xABCD...);const errors = await troll.methods.exitMarket(RBinance.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.
Cointroller
function getAssetsIn(address account) view returns (address[] memory)
- account: The account whose list of entered markets shall be queried.
- RETURN: The address of each market which is currently entered into.
Solidity
Cointroller troll = Cointroller(0xABCD...);address[] memory markets = troll.getAssetsIn(0xMyAccount);
Web3 1.0
const troll = Cointroller.at(0xABCD...);const markets = await troll.methods.getAssetsIn(rTokens).call();
Collateral Factor
A rToken's collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by minting the rToken.
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 Rifi Governance, as market conditions change.
Cointroller
function markets(address rTokenAddress) view returns (bool, uint, bool)
- rTokenAddress: The address of the rToken to check if listed and get the collateral factor for.
- RETURN: Tuple of values (isListed, collateralFactorMantissa, isRified); isListed represents whether the Cointroller recognizes this rToken; collateralFactorMantissa, scaled by 1e18, is multiplied by a supply balance to determine how much value can be borrowed. The isRified boolean indicates whether or not suppliers and borrowers are distributed RIFI tokens.
Solidity
Cointroller troll = Cointroller(0xABCD...);(bool isListed, uint collateralFactorMantissa, bool isRified) = troll.markets(0x3FDA...);
Web3 1.0
const troll = Cointroller.at(0xABCD...);const result = await troll.methods.markets(0x3FDA...).call();const {0: isListed, 1: collateralFactorMantissa, 2: isRified} = 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 Rifi Protocol exclusively uses unsigned integers, Account Liquidity returns either a surplus or shortfall.
Cointroller
function getAccountLiquidity(address account) view returns (uint, uint, uint)
- account: The account whose liquidity shall be calculated.
- 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
Cointroller troll = Cointroller(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 = Cointroller.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.
Cointroller
function closeFactorMantissa() view returns (uint)
- RETURN: The closeFactor, scaled by 1e18, is multiplied by an outstanding borrow balance to determine how much could be closed.
Solidity
Cointroller troll = Cointroller(0xABCD...);uint closeFactor = troll.closeFactorMantissa();
Web3 1.0
const troll = Cointroller.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. For example, if the liquidation incentive is 1.1, liquidators receive an extra 10% of the borrowers collateral for every unit they close.
Cointroller
function liquidationIncentiveMantissa() view returns (uint)
- 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
Cointroller troll = Cointroller(0xABCD...);uint closeFactor = troll.liquidationIncentiveMantissa();
Web3 1.0
const troll = Cointroller.at(0xABCD...);const closeFactor = await troll.methods.liquidationIncentiveMantissa().call();
Key Events
Event | Description |
---|---|
MarketEntered(CToken cToken, address account) | Emitted upon a successful Enter Market. |
MarketExited(CToken cToken, address account) | Emitted upon a successful Exit Market. |
Error Codes
Code | Name | Description |
---|---|---|
0 | NO_ERROR | Not a failure. |
1 | UNAUTHORIZED | The sender is not authorized to perform this action. |
2 | Cointroller_MISMATCH | Liquidation cannot be performed in markets with different Cointrollers. |
3 | INSUFFICIENT_SHORTFALL | The account does not have sufficient shortfall to perform this action. |
4 | INSUFFICIENT_LIQUIDITY | The account does not have sufficient liquidity to perform this action. |
5 | INVALID_CLOSE_FACTOR | The close factor is not valid. |
6 | INVALID_COLLATERAL_FACTOR | The collateral factor is not valid. |
7 | INVALID_LIQUIDATION_INCENTIVE | The liquidation incentive is invalid. |
8 | MARKET_NOT_ENTERED | The market has not been entered by the account. |
9 | MARKET_NOT_LISTED | The market is not currently listed by the Cointroller. |
10 | MARKET_ALREADY_LISTED | An admin tried to list the same market more than once. |
11 | MATH_ERROR | A math calculation error occurred. |
12 | NONZERO_BORROW_BALANCE | The action cannot be performed since the account carries a borrow balance. |
13 | PRICE_ERROR | The Cointroller could not obtain a required price of an asset. |
14 | REJECTION | The Cointroller rejects the action requested by the market. |
15 | SNAPSHOT_ERROR | The Cointroller could not get the account borrows and exchange rate from the market. |
16 | TOO_MANY_ASSETS | Attempted to enter more markets than are currently supported. |
17 | TOO_MUCH_REPAY | Attempted to repay more than is allowed by the protocol. |
Failure Info
Code | Name |
---|---|
0 | ACCEPT_ADMIN_PENDING_ADMIN_CHECK |
1 | ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK |
2 | EXIT_MARKET_BALANCE_OWED |
3 | EXIT_MARKET_REJECTION |
4 | SET_CLOSE_FACTOR_OWNER_CHECK |
5 | SET_CLOSE_FACTOR_VALIDATION |
6 | SET_COLLATERAL_FACTOR_OWNER_CHECK |
7 | SET_COLLATERAL_FACTOR_NO_EXISTS |
8 | SET_COLLATERAL_FACTOR_VALIDATION |
9 | SET_COLLATERAL_FACTOR_WITHOUT_PRICE |
10 | SET_IMPLEMENTATION_OWNER_CHECK |
11 | SET_LIQUIDATION_INCENTIVE_OWNER_CHECK |
12 | SET_LIQUIDATION_INCENTIVE_VALIDATION |
13 | SET_MAX_ASSETS_OWNER_CHECK |
14 | SET_PENDING_ADMIN_OWNER_CHECK |
15 | SET_PENDING_IMPLEMENTATION_OWNER_CHECK |
16 | SET_PRICE_ORACLE_OWNER_CHECK |
17 | SUPPORT_MARKET_EXISTS |
18 | SUPPORT_MARKET_OWNER_CHECK |
RIFI Distribution Speeds
RIFI Speed
The "RIFI speed" unique to each market is an unsigned integer that specifies the amount of RIFI that is distributed, per block, to suppliers and borrowers in each market. This number can be changed for individual markets by calling the _setRifiSpeed method through a successful Rifi Governance proposal.
The following is the formula for calculating the rate that RIFI is distributed to each supported market.
utility = rTokenTotalBorrows * assetPriceutilityFraction = utility / sumOfAllRIFIedMarketUtilitiesmarketRifiSpeed = rifiRate * utilityFraction
RIFI Distributed Per Block (All Markets)
The Cointroller contract’s rifiRate is an unsigned integer that indicates the rate at which the protocol distributes RIFI to market's suppliers or borrowers, every block. The value is the amount of RIFI (in wei), per block, allocated for the markets. Note that not every market has RIFI distributed to its participants (see Market Metadata).
The rifiRate indicates how much RIFI goes to the suppliers or borrowers, so doubling this number shows how much RIFI goes to all suppliers and borrowers combined. The code examples implement reading the amount of RIFI distributed, per block, to all markets.
Cointroller
uint public rifiRate;
Solidity
Cointroller troll = Cointroller(0xABCD...);// RIFI issued per block to suppliers OR borrowers * (1 * 10 ^ 18)uint rifiRate = troll.rifiRate();// Approximate RIFI issued per day to suppliers OR borrowers * (1 * 10 ^ 18)uint rifiRatePerDay = rifiRate * 4 * 60 * 24;// Approximate RIFI issued per day to suppliers AND borrowers * (1 * 10 ^ 18)uint rifiRatePerDayTotal = rifiRatePerDay * 2;
Web3 1.2.6
const Cointroller = new web3.eth.Contract(CointrollerAbi, CointrollerAddress);let rifiRate = await Cointroller.methods.rifiRate().call();rifiRate = rifiRate / 1e18;// RIFI issued to suppliers OR borrowersconst rifiRatePerDay = rifiRate * 4 * 60 * 24;// RIFI issued to suppliers AND borrowersconst rifiRatePerDayTotal = rifiRatePerDay * 2;
RIFI Distributed Per Block (Single Market)
The Cointroller contract has a mapping called rifiSpeeds. It maps rToken addresses to an integer of each market’s RIFI distribution per block. The integer indicates the rate at which the protocol distributes RIFI to markets’ suppliers or borrowers. The value is the amount of RIFI (in wei), per block, allocated for the market. Note that not every market has RIFI distributed to its participants (see Market Metadata).
The speed indicates how much RIFI goes to the suppliers or the borrowers, so doubling this number shows how much RIFI goes to market suppliers and borrowers combined. The code examples implement reading the amount of RIFI distributed, per block, to a single market.
Cointroller
mapping(address => uint) public rifiSpeeds;
Solidity
Cointroller troll = Cointroller(0x123...);address rToken = 0xabc...;// RIFI issued per block to suppliers OR borrowers * (1 * 10 ^ 18)uint rifiSpeed = troll.rifiSpeeds(rToken);// Approximate RIFI issued per day to suppliers OR borrowers * (1 * 10 ^ 18)uint rifiSpeedPerDay = rifiSpeed * 4 * 60 * 24;// Approximate RIFI issued per day to suppliers AND borrowers * (1 * 10 ^ 18)uint rifiSpeedPerDayTotal = rifiSpeedPerDay * 2;
Web3 1.2.6
const rTokenAddress = '0xabc...';const Cointroller = new web3.eth.Contract(CointrollerAbi, CointrollerAddress);let rifiSpeed = await Cointroller.methods.rifiSpeeds(rTokenAddress).call();rifiSpeed = rifiSpeed / 1e18;// RIFI issued to suppliers OR borrowersconst rifiSpeedPerDay = rifiSpeed * 4 * 60 * 24;// RIFI issued to suppliers AND borrowersconst rifiSpeedPerDayTotal = rifiSpeedPerDay * 2;
Claim RIFI
Every Rifi user accrues RIFI for each block they are supplying to or borrowing from the protocol. Users may call the Cointroller's claimRifi method at any time to transfer RIFI accrued to their address.
Cointroller
// Claim all the RIFI accrued by holder in all marketsfunction claimRifi(address holder) public// Claim all the RIFI accrued by holder in specific marketsfunction claimRifi(address holder, RToken[] memory rTokens) public// Claim all the RIFI accrued by specific holders in specific markets for their supplies and/or borrowsfunction claimRifi(address[] memory holders, RToken[] memory rTokens, bool borrowers, bool suppliers) public
Solidity
Cointroller troll = Cointroller(0xABCD...);troll.claimRifi(0x1234...);
Web3 1.2.6
const Cointroller = new web3.eth.Contract(CointrollerAbi, CointrollerAddress);await Cointroller.methods.claimRifi("0x1234...").send({ from: sender });
Market Metadata
The Cointroller contract has an array called getAllMarkets that contains the addresses of each rToken contract. Each address in the getAllMarkets array can be used to fetch a metadata struct in the Cointroller’s markets constant. See the Cointroller Storage contract for the Market struct definition.
Cointroller
RToken[] public getAllMarkets;
Solidity
Cointroller troll = Cointroller(0xABCD...);RToken rTokens[] = troll.getAllMarkets();
Web3 1.2.6
const Cointroller = new web3.eth.Contract(CointrollerAbi, CointrollerAddress);const rTokens = await Cointroller.methods.getAllMarkets().call();const rToken = rTokens[0]; // address of a rToken