Controller

Introduction

The Controller is the risk management layer of the DeFiPIE 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 pToken, 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 Unitroller proxies all logic to the Controller implementation, but storage values are set on the Unitroller. To call Controller functions, use the Controller 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 deposit collateral or borrow in a market, it must be entered first.

Controller

function enterMarkets(address[] calldata pTokens) returns (uint[] memory)
  • msg.sender: The account which shall enter the given markets.

  • pTokens: The addresses of the pToken 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

Controller troll = Controller(0xABCD...);
PToken[] memory pTokens = new PToken[](2);
pTokens[0] = PErc20(0x3FDA...);
pTokens[1] = PEther(0x3FDB...);
uint[] memory errors = troll.enterMarkets(pTokens);

Web3 1.0

const troll = Controller.at(0xABCD...);
const pTokens = [PErc20.at(0x3FDA...), PEther.at(0x3FDB...)];
const errors = await troll.methods.enterMarkets(pTokens).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.

Controller

function exitMarket(address pToken) returns (uint)
  • msg.sender: The account which shall exit the given market.

  • pTokens: The addresses of the pToken market to exit.

  • RETURN: 0 on success, otherwise an Error code.

Solidity

Controller troll = Controller(0xABCD...);
uint error = troll.exitMarket(PToken(0x3FDA...));

Web3 1.0

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

Get Assets In

Get the list of markets an account is currently entered into. In order to deposit 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.

Solidity

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

Web3 1.0

const troll = Controller.at(0xABCD...);
const markets = await troll.methods.getAssetsIn(pTokens).call();

Collateral Factor

A pToken's collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by minting the pToken.

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 Governance, as market conditions change.

Controller

function markets(address pTokenAddress) view returns (bool, uint, bool)
  • pTokenAddress: The address of the pToken to check if listed and get the collateral factor for.

  • RETURN: Tuple of values (isListed, collateralFactorMantissa, isPied); isListed represents whether the controller recognizes this pToken; collateralFactorMantissa, scaled by 1e18, is multiplied by a deposit balance to determine how much value can be borrowed. The isPied boolean indicates whether or not suppliers and borrowers are distributed PIE tokens.

Solidity

Controller troll = Controller(0xABCD...);
(bool isListed, uint collateralFactorMantissa, bool isPied) = troll.markets(0x3FDA...);

Web3 1.0

const troll = Controller.at(0xABCD...);
const result = await troll.methods.markets(0x3FDA...).call();
const {0: isListed, 1: collateralFactorMantissa, 2: isPied} = 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 deposited 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 DeFiPIE 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.

Solidity

Controller troll = Controller(0xABCD...);
(uint error, uint liquidity, uint shortfall) = troll.getAccountLiquidity(msg.caller);
require(error == 0, "contact support");
require(shortfall == 0, "account underwater");
require(liquidity > 0, "account has excess collateral");

Web3 1.0

const troll = Controller.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.

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.

Solidity

Controller troll = Controller(0xABCD...);
uint closeFactor = troll.closeFactorMantissa();

Web3 1.0

const troll = Controller.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.

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.

Solidity

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

Web3 1.0

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

Key Events

Error Codes

Failure Info

PIE Distribution Speeds

PIE Speed

The "PIE speed" unique to each market is an unsigned integer that specifies the amount of PIE that is distributed, per block, to suppliers and borrowers in each market. This number can be changed for individual markets by calling the _setPieSpeed method through a successful DeFiPIE Governance proposal.

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

utility = pTokenTotalBorrows * assetPrice

utilityFraction = utility / sumOfAllPIEedMarketUtilities

marketPieSpeed = pieRate * utilityFraction

PIE Distributed Per Block (All Markets)

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

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

Controller

uint public pieRate;

Solidity

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

// PIE issued per block to suppliers OR borrowers * (1 * 10 ^ 18)
uint pieRate = troll.pieRate();

// Approximate PIE issued per day to suppliers OR borrowers * (1 * 10 ^ 18)
uint pieRatePerDay = pieRate * 4 * 60 * 24;

// Approximate PIE issued per day to suppliers AND borrowers * (1 * 10 ^ 18)
uint pieRatePerDayTotal = pieRatePerDay * 2;

Web3 1.0

const controller = new web3.eth.Contract(controllerAbi, controllerAddress);

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

// PIE issued to suppliers OR borrowers
const pieRatePerDay = pieRate * 4 * 60 * 24;

// PIE issued to suppliers AND borrowers
const pieRatePerDayTotal = pieRatePerDay * 2;

PIE Distributed Per Block (Single Market)

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

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

Controller

mapping(address => uint) public pieSpeeds;

Solidity

Controller troll = Controller(0x123...);
address pToken = 0xabc...;

// PIE issued per block to suppliers OR borrowers * (1 * 10 ^ 18)
uint pieSpeed = troll.pieSpeeds(pToken);

// Approximate PIE issued per day to suppliers OR borrowers * (1 * 10 ^ 18)
uint pieSpeedPerDay = pieSpeed * 4 * 60 * 24;

// Approximate PIE issued per day to suppliers AND borrowers * (1 * 10 ^ 18)
uint pieSpeedPerDayTotal = pieSpeedPerDay * 2;

Web3 1.0

const pTokenAddress = '0xabc...';

const controller = new web3.eth.Contract(controllerAbi, controllerAddress);

let pieSpeed = await controller.methods.pieSpeeds(pTokenAddress).call();
pieSpeed = pieSpeed / 1e18;

// PIE issued to suppliers OR borrowers
const pieSpeedPerDay = pieSpeed * 4 * 60 * 24;

// PIE issued to suppliers AND borrowers
const pieSpeedPerDayTotal = pieSpeedPerDay * 2;

Claim PIE

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

Controller

// Claim all the PIE accrued by holder in all markets
function claimPie(address holder) public

// Claim all the PIE accrued by holder in specific markets
function claimPie(address holder, PToken[] memory pTokens) public

// Claim all the PIE accrued by specific holders in specific markets for their supplies and/or borrows
function claimPie(address[] memory holders, PToken[] memory pTokens, bool borrowers, bool suppliers) public

Solidity

Controller troll = Controller(0xABCD...);
troll.claimPie(0x1234...);

Web3 1.0

const controller = new web3.eth.Contract(controllerAbi, controllerAddress);
await controller.methods.claimPie("0x1234...").send({ from: sender });

Market Metadata

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

Controller

PToken[] public getAllMarkets;

Solidity

Controller troll = Controller(0xABCD...);
PToken pTokens[] = troll.getAllMarkets();

Web3 1.0

const controller = new web3.eth.Contract(controllerAbi, controllerAddress);
const pTokens = await controller.methods.getAllMarkets().call();
const pToken = pTokens[0]; // address of a pToken

Last updated