Unitroller

The Unitroller is the risk management layer of the Source One Market; it determines how much collateral a user is required to maintain, and whether (and by now much) a user can be liquidated. Each time a user interacts with sToken, the Unitroller is asked to approve or deny the transaction.The Unitroller maps user balanes to price (via the Price Oracle) to risk weights (Called Collateral Factors) to make itsdeterminations. Users explicitly list which assets they would like included in their risk scoring, by calling Enter Markets and ExitMarket.

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

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.

function enterMarkets(address[] calldata sTokens) returns (uint[] memory)
  • msg.sender: The account which shall enter the given markets.
  • sTokens: The addresses of the sToken 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

Unitroller troll = Unitroller(0xABCD...);
sToken[] memory sTokens = new sToken[](2);
sTokens[0] = CErc20(0x3FDA...);
sTokens[1] = CEther(0x3FDB...);
uint[] memory errors = troll.enterMarkets(sTokens);

const troll = Unitroller.at(0xABCD...);
const sTokens = [CErc20.at(0x3FDA...), CEther.at(0x3FDB...)];
const errors = await troll.methods.enterMarkets(sTokens).send({from: ...});

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.
Unitroller
function exitMarket(address sToken) returns (uint)
  • msg.sender: The account which shall exit the given market.
  • sTokens: The addresses of the sToken market to exit.
  • RETURN: 0 on success, otherwise an Error code

Unitroller troll = Unitroller(0xABCD...);
sToken[] memory sTokens = new sToken[](2);
sTokens[0] = CErc20(0x3FDA...);
sTokens[1] = CEther(0x3FDB...);
uint[] memory errors = troll.enterMarkets(sTokens);

const troll = Unitroller.at(0xABCD...);
const sTokens = [CErc20.at(0x3FDA...), CEther.at(0x3FDB...)];
const errors = await troll.methods.enterMarkets(sTokens).send({from: ...});

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

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

const troll = Unitroller.at(0xABCD...);
const markets = await troll.methods.getAssetsIn(sTokens).call();

A sToken's collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by minting the sToken.
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 Venus Governance, as market conditions change.
Unitroller
function markets(address vTokenAddress) view returns (bool, uint, bool)
  • sTokenAddress: The address of the sToken to check if listed and get the collateral factor for.
  • RETURN: Tuple of values (isListed, collateralFactorMantissa, isSrc1ed); isListed represents whether the unitroller recognizes this sToken; collateralFactorMantissa, scaled by 1e18, is multiplied by a supply balance to determine how much value can be borrowed. The isSrc1ed boolean indicates whether or not suppliers and borrowers are distributed SRC1 tokens.

Unitroller troll = Unitroller(0xABCD...);
(bool isListed, uint collateralFactorMantissa, bool isSrc1ed) = troll.markets(0x3FDA...);

const troll = Unitroller.at(0xABCD...);
const result = await troll.methods.markets(0x3FDA...).call();
const {0: isListed, 1: collateralFactorMantissa, 2: isSrc1ed} = result;

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 Source One Market exclusively uses unsigned integers, Account Liquidity returns either a surplus or shortfall.

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.

Unitroller troll = Unitroller(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");

const troll = Unitroller.at(0xABCD...);
const result = await troll.methods.getAccountLiquidity(0xBorrower).call();
const {0: error, 1: liquidity, 2: shortfall} = result;

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.

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
Unitroller troll = Unitroller(0xABCD...);
uint closeFactor = troll.closeFactorMantissa();

const troll = Unitroller.at(0xABCD...);
const closeFactor = await troll.methods.closeFactoreMantissa().call();

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.

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.

Unitroller troll = Unitroller(0xABCD...);
uint closeFactor = troll.liquidationIncentiveMantissa();

const troll = Unitroller.at(0xABCD...);
const closeFactor = await troll.methods.liquidationIncentiveMantissa().call();

Event
Description
MarketEntered(sToken sToken, address account)
Emitted upon a successful Enter Market.
MarketExited(sToken sToken, address account)
Emitted upon a successful Exit Market.

Code
Name
Description
0
NO_ERROR
Not a failure.
1
UNAUTHORIZED
The sender is not authorized to perform this action.
2
SRC1TROLLER_MISMATCH
Liquidation cannot be performed in markets with different unitrollers.
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 unitroller.
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 unitroller could not obtain a required price of an asset.
14
REJECTION
The unitroller rejects the action requested by the market.
15
SNAPSHOT_ERROR
The unitroller 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.

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

The “Source One speed” unique to each market is an unsigned integer that specifies the amount of SRC1 that is distributed, per block, to suppliers and borrowers in each market. As market conditions change, Source One speeds are updated to ensure SRC1 is distributed proportional to the utility of each market. Any user can call the Unitroller’s refreshSourceoneSpeeds method at any time in order to update market Source One speeds.
The following is the formula for calculating the rate that SRC1 is distributed to each supported market.
utility = sTokenTotalBorrows * assetPrice
utilityFraction = utility / sumOfAllSourceoneedMarketUtilities
marketSourceoneSpeed = sourceoneRate * utilityFraction

function refreshSourceoneSpeeds(address account) public
  • RETURN: None
  • events: SourceoneSpeedUpdated - An event is emitted for each sToken with the address of the sToken and the new SRC1 distribution speed per block.

Unitroller troll = Unitroller(0xABCD...);
troll.refreshSourceoneSpeeds();

const unitroller = new web3.eth.Contract(unitrollerAbi, unitrollerAddress);
await unitroller.methods.refreshSourceoneSpeeds().send({ from: sender });

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

uint public sourceoneRate;

Unitroller troll = Unitroller(0xABCD...);
// SRC1 issued per block to suppliers OR borrowers * (1 * 10 ^ 18)
uint sourceoneRate = troll.sourceoneRate();
// Approximate SRC1 issued per day to suppliers OR borrowers * (1 * 10 ^ 18)
uint sourceoneRatePerDay = sourceoneRate * 4 * 60 * 24;
// Approximate SRC1 issued per day to suppliers AND borrowers * (1 * 10 ^ 18)
uint sourceoneRatePerDayTotal = sourceoneRatePerDay * 2;

const unitroller = new web3.eth.Contract(unitrollerAbi, unitrollerAddress);
let sourceoneRate = await unitroller.methods.sourceoneRate().call();
sourceoneRate = vsourceoneRate / 1e18;
// SRC1 issued to suppliers OR borrowers
const sourceoneRatePerDay = sourceoneRate * 4 * 60 * 24;
// SRC1 issued to suppliers AND borrowers
const sourceoneRatePerDayTotal = sourceoneRatePerDay * 2;

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

mapping(address => uint) public sourceoneSpeeds;

Unitroller troll = Unitroller(0x123...);
address sToken = 0xabc...;
// SRC1 issued per block to suppliers OR borrowers * (1 * 10 ^ 18)
uint sourceoneSpeed = troll.sourceoneSpeeds(sToken);
// Approximate SRC1 issued per day to suppliers OR borrowers * (1 * 10 ^ 18)
uint sourceoneSpeedPerDay = sourceoneSpeed * 4 * 60 * 24;
// Approximate SRC1 issued per day to suppliers AND borrowers * (1 * 10 ^ 18)
uint sourceoneSpeedPerDayTotal = sourceoneSpeedPerDay * 2;

const sTokenAddress = '0xabc...';
const unitroller = new web3.eth.Contract(unitrollerAbi, unitrollerAddress);
let sourceoneSpeed = await unitroller.methods.sourceoneSpeeds(sTokenAddress).call();
sourceoneSpeed = sourceoneSpeed / 1e18;
// SRC1 issued to suppliers OR borrowers
const sourceoneSpeedPerDay = sourceoneSpeed * 4 * 60 * 24;
// SRC1 issued to suppliers AND borrowers
const sourceoneSpeedPerDayTotal = sourceoneSpeedPerDay * 2;

Every Source One Market user accrues SRC1 for each block they are supplying to or borrowing from the protocol. The protocol automatically transfers accrued SRC1 to a user’s address when the total amount of SRC1 accrued that address (in a market) is greater than the claimSourceoneThreshold, and and the address executes any of the mint, borrow, transfer, liquidateBorrow, repayBorrow, or redeem functions on that market. Separately, users may call the claimSourceone method on any sToken contract at any time for finer grained control over which markets to claim from.

// Claim all the SRC1 accrued by holder in all markets
function claimSourceone(address holder) public
// Claim all the SRC1 accrued by holder in specific markets
function claimSourceone(address holder, sToken[] memory sTokens) public
// Claim all the SRC1 accrued by specific holders in specific markets for their supplies and/or borrows
function claimSourceone(address[] memory holders, sToken[] memory sTokens, bool borrowers, bool suppliers) public
RETURN: The liquidationIncentive, scaled by 1e18, is multiplied by the closed borrow amount from the liquidator to determine how much collateral can be seized.

Unitroller troll = Unitroller(0xABCD...);
troll.claimSourceone(0x1234...);

const unitroller = new web3.eth.Contract(unitrollerAbi, unitrollerAddress);
await unitroller.methods.claimSourceone("0x1234...").send({ from: sender });

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

sToken[] public allMarkets;

Unitroller troll = Unitroller(0xABCD...);
sToken sTokens[] = troll.allMarkets();

const unitroller = new web3.eth.Contract(unitrollerAbi, unitrollerAddress);
const sTokens = await unitroller.methods.allMarkets().call();
const sToken = sTokens[0]; // address of a sToken
Copy link
On this page
Introduction
Architecture
Enter Markets
Exit Market
Get Assets In
Collateral Factor
Get Account Liquidity
Close Factor
Liquidation Incentive
Key Events
Error Codes
Failure Info
Source One Distribution Speeds
SRC1 Distributed Per Block (All Markets)
SRC1 Distributed Per Block (Single Market)
Claim Source One
Market Metadata