Skip to content

Commit

Permalink
Merge pull request #171 from morpho-labs/feat/accrued-interests
Browse files Browse the repository at this point in the history
feat(accrued-interests): add interests library
  • Loading branch information
MathisGD committed Aug 17, 2023
2 parents 71eb4d7 + cc78aed commit de37c00
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 62 deletions.
38 changes: 18 additions & 20 deletions src/Morpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ uint256 constant ORACLE_PRICE_SCALE = 1e36;
uint256 constant LIQUIDATION_CURSOR = 0.3e18;
/// @dev Max liquidation incentive factor.
uint256 constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18;

/// @dev The EIP-712 typeHash for EIP712Domain.
bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

/// @dev The EIP-712 typeHash for Authorization.
bytes32 constant AUTHORIZATION_TYPEHASH =
keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
Expand Down Expand Up @@ -136,8 +134,8 @@ contract Morpho is IMorpho {
require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED);
require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);

// Accrue interests using the previous fee set before changing it.
_accrueInterests(market, id);
// Accrue interest using the previous fee set before changing it.
_accrueInterest(market, id);

fee[id] = newFee;

Expand Down Expand Up @@ -178,7 +176,7 @@ contract Morpho is IMorpho {
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);

_accrueInterests(market, id);
_accrueInterest(market, id);

if (assets > 0) shares = assets.toSharesDown(totalSupply[id], totalSupplyShares[id]);
else assets = shares.toAssetsUp(totalSupply[id], totalSupplyShares[id]);
Expand Down Expand Up @@ -208,7 +206,7 @@ contract Morpho is IMorpho {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);

_accrueInterests(market, id);
_accrueInterest(market, id);

if (assets > 0) shares = assets.toSharesUp(totalSupply[id], totalSupplyShares[id]);
else assets = shares.toAssetsDown(totalSupply[id], totalSupplyShares[id]);
Expand Down Expand Up @@ -240,7 +238,7 @@ contract Morpho is IMorpho {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);

_accrueInterests(market, id);
_accrueInterest(market, id);

if (assets > 0) shares = assets.toSharesUp(totalBorrow[id], totalBorrowShares[id]);
else assets = shares.toAssetsDown(totalBorrow[id], totalBorrowShares[id]);
Expand Down Expand Up @@ -269,7 +267,7 @@ contract Morpho is IMorpho {
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);

_accrueInterests(market, id);
_accrueInterest(market, id);

if (assets > 0) shares = assets.toSharesDown(totalBorrow[id], totalBorrowShares[id]);
else assets = shares.toAssetsUp(totalBorrow[id], totalBorrowShares[id]);
Expand All @@ -296,7 +294,7 @@ contract Morpho is IMorpho {
require(assets != 0, ErrorsLib.ZERO_ASSETS);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);

// Don't accrue interests because it's not required and it saves gas.
// Don't accrue interest because it's not required and it saves gas.

collateral[id][onBehalf] += assets;

Expand All @@ -316,7 +314,7 @@ contract Morpho is IMorpho {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);

_accrueInterests(market, id);
_accrueInterest(market, id);

collateral[id][onBehalf] -= assets;

Expand All @@ -338,7 +336,7 @@ contract Morpho is IMorpho {
require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED);
require(seized != 0, ErrorsLib.ZERO_ASSETS);

_accrueInterests(market, id);
_accrueInterest(market, id);

uint256 collateralPrice = IOracle(market.oracle).price();

Expand Down Expand Up @@ -425,16 +423,16 @@ contract Morpho is IMorpho {
/* INTEREST MANAGEMENT */

/// @inheritdoc IMorpho
function accrueInterests(Market memory market) external {
function accrueInterest(Market memory market) external {
Id id = market.id();
require(lastUpdate[id] != 0, ErrorsLib.MARKET_NOT_CREATED);

_accrueInterests(market, id);
_accrueInterest(market, id);
}

/// @dev Accrues interests for `market`.
/// @dev Accrues interest for `market`.
/// @dev Assumes the given `market` and `id` match.
function _accrueInterests(Market memory market, Id id) internal {
function _accrueInterest(Market memory market, Id id) internal {
uint256 elapsed = block.timestamp - lastUpdate[id];

if (elapsed == 0) return;
Expand All @@ -443,20 +441,20 @@ contract Morpho is IMorpho {

if (marketTotalBorrow != 0) {
uint256 borrowRate = IIrm(market.irm).borrowRate(market);
uint256 accruedInterests = marketTotalBorrow.wMulDown(borrowRate.wTaylorCompounded(elapsed));
totalBorrow[id] = marketTotalBorrow + accruedInterests;
totalSupply[id] += accruedInterests;
uint256 interest = marketTotalBorrow.wMulDown(borrowRate.wTaylorCompounded(elapsed));
totalBorrow[id] = marketTotalBorrow + interest;
totalSupply[id] += interest;

uint256 feeShares;
if (fee[id] != 0) {
uint256 feeAmount = accruedInterests.wMulDown(fee[id]);
uint256 feeAmount = interest.wMulDown(fee[id]);
// The fee amount is subtracted from the total supply in this calculation to compensate for the fact that total supply is already updated.
feeShares = feeAmount.toSharesDown(totalSupply[id] - feeAmount, totalSupplyShares[id]);
supplyShares[id][feeRecipient] += feeShares;
totalSupplyShares[id] += feeShares;
}

emit EventsLib.AccrueInterests(id, borrowRate, accruedInterests, feeShares);
emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
}

lastUpdate[id] = block.timestamp;
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/IIrm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ import {Market} from "./IMorpho.sol";
interface IIrm {
/// @notice Returns the borrow rate of a `market`.
function borrowRate(Market memory market) external returns (uint256);

/// @notice Returns the borrow rate of a `market` without modifying the IRM's storage.
function borrowRateView(Market memory market) external view returns (uint256);
}
6 changes: 3 additions & 3 deletions src/interfaces/IMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ interface IMorpho is IFlashLender {

/// @notice Supplies the given `assets` of collateral to the given `market` on behalf of `onBehalf`,
/// optionally calling back the caller's `onMorphoSupplyCollateral` function with the given `data`.
/// @dev Interests are not accrued since it's not required and it saves gas.
/// @dev Interest are not accrued since it's not required and it saves gas.
/// @dev Supplying a large amount can overflow and revert without any error message.
/// @param market The market to supply collateral to.
/// @param assets The amount of collateral to supply.
Expand Down Expand Up @@ -235,8 +235,8 @@ interface IMorpho is IFlashLender {
/// @param signature The signature.
function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;

/// @notice Accrues interests for `market`.
function accrueInterests(Market memory market) external;
/// @notice Accrues interest for `market`.
function accrueInterest(Market memory market) external;

/// @notice Returns the data stored on the different `slots`.
function extsload(bytes32[] memory slots) external view returns (bytes32[] memory res);
Expand Down
6 changes: 3 additions & 3 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ library EventsLib {
/// @param usedNonce The nonce that was used.
event IncrementNonce(address indexed caller, address indexed authorizer, uint256 usedNonce);

/// @notice Emitted when accruing interests.
/// @notice Emitted when accruing interest.
/// @param id The market id.
/// @param prevBorrowRate The previous borrow rate.
/// @param accruedInterests The amount of interest accrued.
/// @param interest The amount of interest accrued.
/// @param feeShares The amount of shares minted as fee.
event AccrueInterests(Id indexed id, uint256 prevBorrowRate, uint256 accruedInterests, uint256 feeShares);
event AccrueInterest(Id indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares);
}
106 changes: 106 additions & 0 deletions src/libraries/periphery/MorphoLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {Id, Market, IMorpho} from "../../interfaces/IMorpho.sol";
import {IIrm} from "../../interfaces/IIrm.sol";

import {MathLib} from "../MathLib.sol";
import {MarketLib} from "../MarketLib.sol";
import {SharesMathLib} from "../SharesMathLib.sol";
import {MorphoStorageLib} from "./MorphoStorageLib.sol";

/// @title MorphoLib
/// @author Morpho Labs
/// @custom:contact security@morpho.xyz
/// @notice Helper library exposing getters with the expected value after interest accrual.
/// @dev This library is not used in Morpho itself and is intended to be used by integrators.
/// @dev The getter to retrieve the expected total borrow shares is not exposed because interest accrual does not apply to it.
/// The value can be queried directly on Morpho using `totalBorrowShares`.
library MorphoLib {
using MathLib for uint256;
using MarketLib for Market;
using SharesMathLib for uint256;

function expectedAccrueInterest(IMorpho morpho, Market memory market)
internal
view
returns (uint256 totalSupply, uint256 toralBorrow, uint256 totalSupplyShares)
{
Id id = market.id();

bytes32[] memory slots = new bytes32[](5);
slots[0] = MorphoStorageLib.totalSupplySlot(id);
slots[1] = MorphoStorageLib.totalBorrowSlot(id);
slots[2] = MorphoStorageLib.totalSupplySharesSlot(id);
slots[3] = MorphoStorageLib.feeSlot(id);
slots[4] = MorphoStorageLib.lastUpdateSlot(id);

bytes32[] memory values = morpho.extsload(slots);
totalSupply = uint256(values[0]);
toralBorrow = uint256(values[1]);
totalSupplyShares = uint256(values[2]);
uint256 fee = uint256(values[3]);
uint256 lastUpdate = uint256(values[4]);

uint256 elapsed = block.timestamp - lastUpdate;

if (elapsed == 0) return (totalSupply, toralBorrow, totalSupplyShares);

if (toralBorrow != 0) {
uint256 borrowRate = IIrm(market.irm).borrowRateView(market);
uint256 interest = toralBorrow.wMulDown(borrowRate.wTaylorCompounded(elapsed));
toralBorrow += interest;
totalSupply += interest;

if (fee != 0) {
uint256 feeAmount = interest.wMulDown(fee);
// The fee amount is subtracted from the total supply in this calculation to compensate for the fact that total supply is already updated.
uint256 feeShares = feeAmount.toSharesDown(totalSupply - feeAmount, totalSupplyShares);

totalSupplyShares += feeShares;
}
}
}

function expectedTotalSupply(IMorpho morpho, Market memory market) internal view returns (uint256 totalSupply) {
(totalSupply,,) = expectedAccrueInterest(morpho, market);
}

function expectedTotalBorrow(IMorpho morpho, Market memory market) internal view returns (uint256 totalBorrow) {
(, totalBorrow,) = expectedAccrueInterest(morpho, market);
}

function expectedTotalSupplyShares(IMorpho morpho, Market memory market)
internal
view
returns (uint256 totalSupplyShares)
{
(,, totalSupplyShares) = expectedAccrueInterest(morpho, market);
}

/// @dev Warning: It does not work for `feeRecipient` because their supply shares increase is not taken into account.
function expectedSupplyBalance(IMorpho morpho, Market memory market, address user)
internal
view
returns (uint256)
{
Id id = market.id();
uint256 supplyShares = morpho.supplyShares(id, user);
(uint256 totalSupply,, uint256 totalSupplyShares) = expectedAccrueInterest(morpho, market);

return supplyShares.toAssetsDown(totalSupply, totalSupplyShares);
}

function expectedBorrowBalance(IMorpho morpho, Market memory market, address user)
internal
view
returns (uint256)
{
Id id = market.id();
uint256 borrowShares = morpho.borrowShares(id, user);
uint256 totalBorrowShares = morpho.totalBorrowShares(id);
(, uint256 totalBorrow,) = expectedAccrueInterest(morpho, market);

return borrowShares.toAssetsUp(totalBorrow, totalBorrowShares);
}
}
Loading

0 comments on commit de37c00

Please sign in to comment.