Controller

Introduction

The Contoller is the risk management layer of the Ram 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 Controller is asked to approve or deny the transaction.

The Controller 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 Controller is implemented as an upgradeable proxy. The ControllerProxy proxies all logic to the Controller implementation, but storage values are set on the ControllerProxy. To call Controller functions, use the Controller ABI on the ControllerProxy 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.

Controller

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.

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.

Controller

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.

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.

Controller

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.

Web3 1.0

const markets = await controller.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 admin, as market conditions change.

Controller

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, isRammed); isListed represents whether the controller recognizes this rToken; collateralFactorMantissa, scaled by 1e18, is multiplied by a supply balance to determine how much value can be borrowed. The isRammed boolean indicates whether or not suppliers and borrowers are distributed RAM tokens.

Web3 1.0

const result = await controll.methods.markets('0x3333....').call();
const {0: isListed, 1: collateralFactorMantissa, 2: isRammed} = 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 Ram Protocol exclusively uses unsigned integers, Account Liquidity returns either a surplus or shortfall.

Controller

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.

Web3 1.0

const result = await controll.methods.getAccountLiquidity('0xBBBB....').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.

Controller

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.

Web3 1.0

const closeFactor = await controll.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.

Controller

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.

Web3 1.0

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

Key Events

Event

Description

MarketEntered(RToken rToken, address account)

Emitted upon a successful Enter Market.

MarketExited(RToken rToken, 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

CONTROLLER_MISMATCH

Liquidation cannot be performed in markets with different controllers.

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 controller.

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 controller could not obtain a required price of an asset.

14

REJECTION

The controller rejects the action requested by the market.

15

SNAPSHOT_ERROR

The controller 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

RAM Distribution Speeds

RAM Speed

The "RAM speed" unique to each market is an unsigned integer that specifies the amount of RAM that is distributed, per block, to suppliers and borrowers in each market. This number can be changed for individual markets by calling the _setRamSpeed method through admin or auto decay.

The following is the formula for calculating the rate that RAM is distributed to each supported market.

utility = rTokenTotalBorrows * assetPrice

utilityFraction = utility / sumOfAllRammedMarketUtilities

marketRamSpeed = ramRate * utilityFraction

RAM Distributed Per Block (All Markets)

The Controller contract’s ramRate is an unsigned integer that indicates the rate at which the protocol distributes RAM to markets’ suppliers or borrowers, every ThunderCore block. The value is the amount of RAM (in wei), per block, allocated for the markets. Note that not every market has RAM distributed to its participants (see Market Metadata).

The ramRate indicates how much RAM goes to the suppliers or borrowers, so doubling this number shows how much RAM goes to all suppliers and borrowers combined. The code examples implement reading the amount of RAM distributed, per ThunderCore block, to all markets.

Controller

uint public ramRate;

Web3 1.2.6

const controller = new web3.eth.Contract(abi, address);

let ramRate = await controller.methods.ramRate().call();
ramRate = ramRate / 1e18;

// RAM issued to suppliers OR borrowers
const ramRatePerDay = ramRate * 60 * 60 * 24;

// RAM issued to suppliers AND borrowers
const ramRatePerDayTotal = ramRatePerDay * 2;

RAM Distributed Per Block (Single Market)

The Controller contract has a mapping called ramSpeeds. It maps rToken addresses to an integer of each market’s RAM distribution per ThunderCore block. The integer indicates the rate at which the protocol distributes RAM to markets’ suppliers or borrowers. The value is the amount of RAM (in wei), per block, allocated for the market. Note that not every market has RAM distributed to its participants (see Market Metadata).

The speed indicates how much RAM goes to the suppliers or the borrowers, so doubling this number shows how much RAM goes to market suppliers and borrowers combined. The code examples implement reading the amount of RAM distributed, per ThunderCore block, to a single market.

Controller

mapping(address => uint) public ramSpeeds;

Web3 1.2.6

const controller = new web3.eth.Contract(abi, address);

let ramSpeed = await controller.methods.ramSpeeds(rTokenAddress).call();
ramSpeed = ramSpeed / 1e18;

// RAM issued to suppliers OR borrowers
const ramSpeedPerDay = ramSpeed * 60 * 60 * 24;

// RAM issued to suppliers AND borrowers
const ramSpeedPerDayTotal = ramSpeedPerDay * 2;

Claim RAM

Every Ram user accrues RAM for each block they are supplying to or borrowing from the protocol. Users may call the Controller's claimRam method at any time to transfer RAM accrued to their address.

Controller

// Claim all the RAM accrued by holder in all markets
function claimRam(address holder) public

// Claim all the RAM accrued by holder in specific markets
function claimRam(address holder, RToken[] memory rTokens) public

// Claim all the RAM accrued by specific holders in specific markets for their supplies and/or borrows
function claimRam(address[] memory holders, RToken[] memory rTokens, bool borrowers, bool suppliers) public

Web3 1.2.6

await controller.methods.claimRam("0x1234...").send({ from: sender });

Market Metadata

The Controller 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 Controller’s markets constant.

Controller

RToken[] public getAllMarkets;

Web3 1.2.6

const rTokens = await controller.methods.getAllMarkets().call();
const rToken = rTokens[0]; // address of a rToken

Last updated