Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(accrued-interests): add interests library #171

Merged
merged 34 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b7fc2b1
feat(accrued-interests): add interests library
Rubilmax Jul 25, 2023
569fb46
Merge branch 'main' of github.com:morpho-labs/blue into feat/accrued-…
Rubilmax Aug 7, 2023
8198775
refactor(accrued-interests): remove overwhelming return variables
Rubilmax Aug 7, 2023
5378fc0
fix(storage-slots): fix bad slots
Rubilmax Aug 7, 2023
f35b640
Merge branch 'feat/public-accrue-interests' of github.com:morpho-labs…
Rubilmax Aug 11, 2023
84022c7
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/a…
MerlinEgalite Aug 15, 2023
61ead50
refactor: rename blue -> morpho
MerlinEgalite Aug 15, 2023
61563cc
feat: add updated getters
MerlinEgalite Aug 15, 2023
1979afa
refactor: user view and fix computation
MerlinEgalite Aug 15, 2023
fdb3d57
Merge branch 'feat/accrued-interests' of github.com:morpho-labs/morph…
MerlinEgalite Aug 15, 2023
84b8889
refactor: rename variables
MerlinEgalite Aug 15, 2023
516b728
chore: move files in a periphery folder
MerlinEgalite Aug 16, 2023
a1ab09c
refactor: renaming proposal
MerlinEgalite Aug 16, 2023
7b480c0
fix: correct naming for is auhtorized
MerlinEgalite Aug 16, 2023
0a5bd58
docs: add comments
MerlinEgalite Aug 16, 2023
38351ab
fix: iirm interface
MerlinEgalite Aug 16, 2023
53bb41f
refactor: harmonize morpho and lib
MerlinEgalite Aug 16, 2023
2736736
refactor: add slot to getters
MerlinEgalite Aug 16, 2023
31e1ef0
Merge pull request #307 from morpho-labs/feat/add-updated-getters
MerlinEgalite Aug 16, 2023
0f83222
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/a…
MerlinEgalite Aug 16, 2023
8a3eec8
refactor: accrued interests -> interests in events
MerlinEgalite Aug 16, 2023
56329a3
refactor: renaming
MerlinEgalite Aug 16, 2023
8a5eb4e
refactor: interests -> interest
MerlinEgalite Aug 16, 2023
c567812
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/a…
MerlinEgalite Aug 16, 2023
f162c8b
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/a…
MerlinEgalite Aug 16, 2023
cf38cec
Merge branch 'feat/accrued-interests' of github.com:morpho-labs/morph…
MerlinEgalite Aug 16, 2023
1961394
Merge pull request #329 from morpho-labs/refactor/interest
MathisGD Aug 16, 2023
5085207
refactor: remove uselesss line spaces
MerlinEgalite Aug 16, 2023
dc82f52
test: add MorphoLib test
MerlinEgalite Aug 17, 2023
be93dd1
feat: add missing slot getters
MerlinEgalite Aug 17, 2023
e937427
test: add MorphoStorageLib tests
MerlinEgalite Aug 17, 2023
e35f684
refactor: apply suggestions
MerlinEgalite Aug 17, 2023
f9a46f4
docs: apply suggestion
MerlinEgalite Aug 17, 2023
cc78aed
refactor: bundle suggestions
MerlinEgalite Aug 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions src/Morpho.sol
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
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);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
}
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 [email protected]
/// @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 total borrow shares is not exposed because interest accrual does not apply to it.
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
/// 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 virtualAccrueInterest(IMorpho morpho, Market memory market)
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
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]);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

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,,) = virtualAccrueInterest(morpho, market);
}

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

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

/// @dev Warning: It does not work for `feeRecipient` because the increase of supply shares during the interest accrual is not taken into account.
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
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) = virtualAccrueInterest(morpho, market);

return supplyShares.toAssetsDown(totalSupply, totalSupplyShares);
}
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

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,) = virtualAccrueInterest(morpho, market);

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