Weekly recaps

Community translations

Powered By GitBook

Developer Guide

Dissecting Inverse Finance's Anchor protocol

Inverse Financeβs Anchor protocol is a crypto native federal reserve that offers borrowing, lending, stablecoins and synthetic assets. It is a fork of Compound Finance and the main contracts are not changed. The only change to the protocol is the PriceOracle implementation which plugs into Chainlink feeds.

Users can supply their ETH to the protocol for borrowers in order to earn interest. They can also enable their provided ETH as collaterals so that they can borrow DOLA (Inverse Financeβs stablecoin). A user can borrow up to 55% of his deposited collateral, which equates to a 180% collateralization ratio. Liquidators can liquidate debts that are under-collateralized and earn a 13% bonus on the collateral purchased. When a liquidation happens, the borrower will lose his collateral up to the value of his debt, and can keep the rest of the collateral and his borrowed DOLA.

β

Depositing ETH

A user can deposit ETH into the protocol to receive cTokens by calling the CEther.sol contractβs payable method mint.

1.

It first accrues interest.

1

// blockDelta is the number of blocks between the current // block and the last block where interest accrual happened.

2

β

3

(MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumberPrior);

Copied!

1a. It gets the previous financial data and the borrow rate from the contract.

1

// previous ETH balance = current contract ETH balance - ETH sent to contract

2

uint cashPrior = subUInt(address(this).balance, msg.value);

3

uint borrowsPrior = totalBorrows;

4

uint reservesPrior = totalReserves;

5

uint borrowIndexPrior = borrowIndex;

6

β

7

uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);

Copied!

1b. It gets the current borrow rate (If the capital utilization rate is not higher than kink, which is a utilization point at which the jump multiplier is applied).

1

// borrow rate = utilization rate x multiplier per block + base rate per block

2

return util.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);

Copied!

1c. It gets the current borrow rate (If the capital utilization rate is higher than kink).

1

// borrow rate = (kink x multiplier per block + base rate per block) + ((utilization rate - kink) x jump multiplier per block)

2

uint normalRate = kink.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);

3

uint excessUtil = util.sub(kink);

4

return excessUtil.mul(jumpMultiplierPerBlock).div(1e18).add(normalRate);

Copied!

1d. It calculates the simple interest factor, interest accumulated and the new financial data.

1

// simple interest factor = borrow rate x number of blocks since last update

2

(mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa: borrowRateMantissa}), blockDelta);

3

β

4

// interest accumulated = simple interest factor x prior total borrow amount

5

(mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior);

6

β

7

// new total borrow amount = interest accumulated + prior total borrow amount

8

(mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior);

9

β

10

// new total reserves amount = reserve factor x interest accumulated + prior total reserves

11

(mathErr, totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa: reserveFactorMantissa}), interestAccumulated, reservesPrior);

12

β

13

// new borrow index = simple interestAccumulated factor x prior borrow index + prior borrow index

14

(mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);

Copied!

2.

It mints cTokens.

2a. It gets the exchange rate of ETH to cEther.

1

// cash plus borrow minus reserves = total cash + total borrows - total reserves

2

(mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);

3

β

4

// exchange rate = cash plus borrow minus reserves / total supply of cEther

5

(mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);

Copied!

2b. It gets the actual amount to mint.

1

// mintTokens = actualMintAmount / exchangeRate

2

(vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(vars.actualMintAmount, Exp({mantissa: vars.exchangeRateMantissa}));

Copied!

2c. It adds the amount to the accountβs balance.

1

// new total supply = total supply + tokens to mint

2

(vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens);

3

β

4

// new account tokens = account tokens + tokens to mint

5

(vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens);

6

β

7

totalSupply = vars.totalSupplyNew;

8

accountTokens[minter] = vars.accountTokensNew;

Copied!

β

Allowing your deposits to be turned into collateral

A user can turn his cTokens into collaterals by calling the ComptrollerG6.sol contractβs method enterMarkets. The user provides an array of cTokens addresses as the markets to enter and the method loops through each of them to the borrowersβ βassets inβ for liquidity calculations.

1.

It turns on the userβs account membership for the particular market.

1

marketToJoin.accountMembership[borrower] = true;

Copied!

2.

It adds the cToken to the userβs account assets.

1

accountAssets[borrower].push(cToken);

Copied!

β

Borrowing DOLA against your collaterals

After becoming a marketβs member, a user can now borrow DOLA against his collaterals. It is done so by calling the CErc20.sol contractβs borrow method.

1.

It first accrues interest (It calls the same method as the first operation Depositing ETH).

2.

It checks the userβs borrow eligibility (looping through each market the user wants to enter)

2a. It gets the marketβs snapshot.

1

(oErr, vars.cTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot(account);

Copied!

2b. It gets the marketβs collateral factor and exchange rate.

1

vars.collateralFactor = Exp({mantissa: markets[address(asset)].collateralFactorMantissa});

2

vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa});

Copied!

2c. It gets the asset market price from its oracle.

1

vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset);

2

vars.oraclePrice = Exp({mantissa: vars.oraclePriceMantissa});

Copied!

2d. It calculates the sum of collateral and the sum of borrow plus effects.

1

// Pre-compute a conversion factor from tokens -> ether (normalized price value)

2

vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice);

3

β

4

// sumCollateral += tokensToDenom * cTokenBalance

5

vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.cTokenBalance, vars.sumCollateral);

6

β

7

// sumBorrowPlusEffects += oraclePrice * borrowBalance

8

vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, vars.borrowBalance, vars.sumBorrowPlusEffects);

9

β

10

// Calculate effects of interacting with cTokenModify

11

if (asset == cTokenModify) {

12

// redeem effect

13

// sumBorrowPlusEffects += tokensToDenom * redeemTokens

14

vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.tokensToDenom, redeemTokens, vars.sumBorrowPlusEffects);

15

β

16

// borrow effect

17

// sumBorrowPlusEffects += oraclePrice * borrowAmount

18

vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, borrowAmount, vars.sumBorrowPlusEffects);

19

}

Copied!

2e. It checks the sum of collateral is enough to cover the hypothetical sum of borrow plus effects and it reverts if there is a shortfall.

1

// The last returned value is "shortfall".

2

if (vars.sumCollateral > vars.sumBorrowPlusEffects) {

3

return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0);

4

} else {

5

return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral);

6

}

7

β

8

β

9

if (shortfall > 0) {

10

return uint(Error.INSUFFICIENT_LIQUIDITY);

11

}

Copied!

3.

It checks the market has enough cash reserves for borrowing.

1

if (getCashPrior() < borrowAmount) {

2

return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.BORROW_CASH_NOT_AVAILABLE);

3

}

Copied!

4.

It calculates the marketβs new total borrow amount by adding the new borrow amount to the existing borrow amounts.

1

(vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower);

2

(vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount);

3

(vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount);

Copied!

5.

It transfers the loan to the borrower and accounts for the change.

1

token.transfer(borrower, borrowAmount);

2

β

3

accountBorrows[borrower].principal = vars.accountBorrowsNew;

4

accountBorrows[borrower].interestIndex = borrowIndex;

Copied!

β

Repaying a loan

A user can repay his outstanding debt by calling CErc20.solβs repayBorrow method.

1.

It first accrues interest just like depositing ETH or borrowing DOLA.

2.

It checks if repaying a loan is allowed.

1

uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount);

2

if (allowed != 0) {

3

return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed), 0);

4

}

Copied!

3.

It calculates the outstanding debt amount.

1

// recent borrow balance = borrower's borrow balance x market's borrow index Γ· borrower's borrow index

2

β

3

vars.borrowerIndex = accountBorrows[borrower].interestIndex;

4

BorrowSnapshot storage borrowSnapshot = accountBorrows[account];

5

(mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex);

6

(mathErr, recentBorrowBalance) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex);

Copied!

4.

It transfers the repay amount from the user back to the borrow market. Programming in Solidity in general is very defensive, it is good to check your mathematical operation does what you expect it to do. Overflow can and will happen.

1

EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying);

2

uint balanceBefore = EIP20Interface(underlying).balanceOf(address(this));

3

token.transferFrom(from, address(this), amount);

4

uint balanceAfter = EIP20Interface(underlying).balanceOf(address(this));

5

require(balanceAfter >= balanceBefore, "TOKEN_TRANSFER_IN_OVERFLOW");

Copied!

5.

It updates the marketβs stats.

1

(vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount);

2

(vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount);

3

accountBorrows[borrower].principal = vars.accountBorrowsNew;

4

accountBorrows[borrower].interestIndex = borrowIndex;

5

totalBorrows = vars.totalBorrowsNew;

Copied!

β

Liquidating a loan

Liquidators (usually bots) can liquidate an under-collateralized loan by calling CErc20.solβs method liquidateBorrow.

1.

It first accrues interest. Every time when something happens, interest has to be accrued.

2.

Then the market in which to seize collateral from the borrower also has to accrue interest.

1

cTokenCollateral.accrueInterest();

Copied!

3.

It then checks the liquidation is allowed. The same method getAccountLiquidityInternal from borrowing is used to calculate if the debt position has any shortfalls.

1

(Error err, , uint shortfall) = getAccountLiquidityInternal(borrower);

2

if (err != Error.NO_ERROR) {

3

return uint(err);

4

}

5

if (shortfall == 0) {

6

return uint(Error.INSUFFICIENT_SHORTFALL);

7

}

Copied!

4.

The method fetches the borrowerβs borrow balance and makes sure the liquidator does not pay more than the borrow balance times the close factor (ranging between 0.05 and 0.9).

1

uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored(borrower);

2

uint maxClose = mul_ScalarTruncate(Exp({mantissa: closeFactorMantissa}), borrowBalance);

3

if (repayAmount > maxClose) {

4

return uint(Error.TOO_MUCH_REPAY);

5

}

Copied!

5.

A borrower cannot liquidate himself.

1

if (borrower == liquidator) {

2

return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0);

3

}

Copied!

6.

It calls repayBorrowFresh, which contains the same logic for when a borrower repays his own loan.

7.

It calculates the amount of collateral to seize from the borrower.

1

// Get DOLA price

2

uint priceBorrowedMantissa = oracle.getUnderlyingPrice(CToken(cTokenBorrowed));

3

// Get ETH price

4

uint priceCollateralMantissa = oracle.getUnderlyingPrice(CToken(cTokenCollateral));

5

β

6

// seize amount = actual repay amount x liquidation incentive x price borrowed Γ· price collateral

7

// seize tokens = seize amount Γ· exchange rate

8

β

9

uint exchangeRateMantissa = CToken(cTokenCollateral).exchangeRateStored(); // Note: reverts on error

10

uint seizeTokens;

11

Exp memory numerator;

12

Exp memory denominator;

13

Exp memory ratio;

14

β

15

numerator = mul_(Exp({mantissa: liquidationIncentiveMantissa}), Exp({mantissa: priceBorrowedMantissa}));

16

denominator = mul_(Exp({mantissa: priceCollateralMantissa}), Exp({mantissa: exchangeRateMantissa}));

17

ratio = div_(numerator, denominator);

18

β

19

seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount);

Copied!

8.

It seizes the tokens from the borrower.

1

// new borrower token balance = current borrower token balance - seized tokens

2

β

3

// new liquidator token balance = current liquidator token balance + seized tokens

4

β

5

(mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens);

6

(mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens);

7

β

8

accountTokens[borrower] = borrowerTokensNew;

9

accountTokens[liquidator] = liquidatorTokensNew;

Copied!

β

The FED

DOLA is a stablecoin that pegs to the USD. The stabilizer, Curve metapool and the Fed together attempt to stabilize DOLA price.

The chair of the contract Fed.sol has the right to exercise expansionary and contractionary monetary policy on DOLA supply.

Monetary expansion

The chair can call the method expansion to mint DOLA as well as its cToken, which increases DOLA supply.

1

function expansion(uint amount) public {

2

require(msg.sender == chair, "ONLY CHAIR");

3

underlying.mint(address(this), amount);

4

require(ctoken.mint(amount) == 0, 'Supplying failed');

5

supply = supply.add(amount);

6

emit Expansion(amount);

7

}

Copied!

Monetary contraction

The chair can call the method contraction to burn DOLA as well as its cToken, which decreases DOLA supply.

1

function contraction(uint amount) public {

2

require(msg.sender == chair, "ONLY CHAIR");

3

require(amount <= supply, "AMOUNT TOO BIG"); // can't burn profits

4

require(ctoken.redeemUnderlying(amount) == 0, "Redeem failed");

5

underlying.burn(amount);

6

supply = supply.sub(amount);

7

emit Contraction(amount);

8

}

Copied!

Taking profit

If the Fedβs underlying asset balance in a cToken contract is greater than the DOLA supply it created, it has the option to take profit and sends it to gov.

1

function takeProfit() public {

2

uint underlyingBalance = ctoken.balanceOfUnderlying(address(this));

3

uint profit = underlyingBalance.sub(supply);

4

if(profit > 0) {

5

require(ctoken.redeemUnderlying(profit) == 0, "Redeem failed");

6

underlying.transfer(gov, profit);

7

}

8

}

Copied!

β

This developer guide has been created by the Inverse.Finance community For questions, join the friendly Discord community.

Credit: 0xkowloon (Discord name)

β

β

β

Last modified 6mo ago