Skip to content

Commit

Permalink
Merge pull request #109 from morpho-labs/refactor/morpho-utils
Browse files Browse the repository at this point in the history
Refactor: shares management
  • Loading branch information
Rubilmax authored Jul 21, 2023
2 parents f905e62 + 86fc677 commit 3f206ae
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 91 deletions.
58 changes: 26 additions & 32 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import {IIrm} from "src/interfaces/IIrm.sol";
import {IERC20} from "src/interfaces/IERC20.sol";

import {Errors} from "./libraries/Errors.sol";
import {MathLib} from "src/libraries/MathLib.sol";
import {SharesMath} from "src/libraries/SharesMath.sol";
import {FixedPointMathLib} from "src/libraries/FixedPointMathLib.sol";
import {Id, Market, MarketLib} from "src/libraries/MarketLib.sol";
import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";

uint256 constant WAD = 1e18;
uint256 constant ALPHA = 0.5e18;

contract Blue {
using MathLib for uint256;
using MarketLib for Market;
using SharesMath for uint256;
using FixedPointMathLib for uint256;
using SafeTransferLib for IERC20;
using MarketLib for Market;

// Storage.

Expand Down Expand Up @@ -108,14 +110,9 @@ contract Blue {

accrueInterests(market, id);

if (totalSupply[id] == 0) {
supplyShare[id][onBehalf] = WAD;
totalSupplyShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
supplyShare[id][onBehalf] += shares;
totalSupplyShares[id] += shares;
}
uint256 shares = amount.toSharesDown(totalSupply[id], totalSupplyShares[id]);
supplyShare[id][onBehalf] += shares;
totalSupplyShares[id] += shares;

totalSupply[id] += amount;

Expand All @@ -130,7 +127,7 @@ contract Blue {

accrueInterests(market, id);

uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
uint256 shares = amount.toSharesUp(totalSupply[id], totalSupplyShares[id]);
supplyShare[id][onBehalf] -= shares;
totalSupplyShares[id] -= shares;

Expand All @@ -151,14 +148,9 @@ contract Blue {

accrueInterests(market, id);

if (totalBorrow[id] == 0) {
borrowShare[id][onBehalf] = WAD;
totalBorrowShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
borrowShare[id][onBehalf] += shares;
totalBorrowShares[id] += shares;
}
uint256 shares = amount.toSharesUp(totalBorrow[id], totalBorrowShares[id]);
borrowShare[id][onBehalf] += shares;
totalBorrowShares[id] += shares;

totalBorrow[id] += amount;

Expand All @@ -175,7 +167,7 @@ contract Blue {

accrueInterests(market, id);

uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
uint256 shares = amount.toSharesDown(totalBorrow[id], totalBorrowShares[id]);
borrowShare[id][onBehalf] -= shares;
totalBorrowShares[id] -= shares;

Expand Down Expand Up @@ -226,10 +218,11 @@ contract Blue {
require(!isHealthy(market, id, borrower), Errors.HEALTHY_POSITION);

// The liquidation incentive is 1 + ALPHA * (1 / LLTV - 1).
uint256 incentive = WAD + ALPHA.wMul(WAD.wDiv(market.lltv) - WAD);
uint256 repaid =
seized.wMul(market.collateralOracle.price()).wDiv(incentive).wDiv(market.borrowableOracle.price());
uint256 repaidShares = repaid.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
uint256 incentive = WAD + ALPHA.mulWadDown(WAD.divWadDown(market.lltv) - WAD);
uint256 repaid = seized.mulWadUp(market.collateralOracle.price()).divWadUp(incentive).divWadUp(
market.borrowableOracle.price()
);
uint256 repaidShares = repaid.toSharesDown(totalBorrow[id], totalBorrowShares[id]);

borrowShare[id][borrower] -= repaidShares;
totalBorrowShares[id] -= repaidShares;
Expand All @@ -239,7 +232,7 @@ contract Blue {

// Realize the bad debt if needed.
if (collateral[id][borrower] == 0) {
uint256 badDebt = borrowShare[id][borrower].wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]);
uint256 badDebt = borrowShare[id][borrower].toAssetsUp(totalBorrow[id], totalBorrowShares[id]);
totalSupply[id] -= badDebt;
totalBorrow[id] -= badDebt;
totalBorrowShares[id] -= borrowShare[id][borrower];
Expand Down Expand Up @@ -267,14 +260,14 @@ contract Blue {

if (marketTotalBorrow != 0) {
uint256 borrowRate = market.irm.borrowRate(market);
uint256 accruedInterests = marketTotalBorrow.wMul(borrowRate).wMul(block.timestamp - lastUpdate[id]);
uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate * (block.timestamp - lastUpdate[id]));
totalBorrow[id] = marketTotalBorrow + accruedInterests;
totalSupply[id] += accruedInterests;

if (fee[id] != 0) {
uint256 feeAmount = accruedInterests.wMul(fee[id]);
uint256 feeAmount = accruedInterests.mulWadDown(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.
uint256 feeShares = feeAmount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id] - feeAmount);
uint256 feeShares = feeAmount.mulDivDown(totalSupplyShares[id], totalSupply[id] - feeAmount);
supplyShare[id][feeRecipient] += feeShares;
totalSupplyShares[id] += feeShares;
}
Expand All @@ -288,10 +281,11 @@ contract Blue {
function isHealthy(Market calldata market, Id id, address user) private view returns (bool) {
uint256 borrowShares = borrowShare[id][user];
if (borrowShares == 0) return true;

// totalBorrowShares[id] > 0 when borrowShares > 0.
uint256 borrowValue =
borrowShares.wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]).wMul(market.borrowableOracle.price());
uint256 collateralValue = collateral[id][user].wMul(market.collateralOracle.price());
return collateralValue.wMul(market.lltv) >= borrowValue;
borrowShares.toAssetsUp(totalBorrow[id], totalBorrowShares[id]).mulWadUp(market.borrowableOracle.price());
uint256 collateralValue = collateral[id][user].mulWadDown(market.collateralOracle.price());
return collateralValue.mulWadDown(market.lltv) >= borrowValue;
}
}
58 changes: 58 additions & 0 deletions src/libraries/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/

uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;

uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}

function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}

function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}

function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}

/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/

function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) }

// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}

function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) }

// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
}
21 changes: 0 additions & 21 deletions src/libraries/MathLib.sol

This file was deleted.

37 changes: 37 additions & 0 deletions src/libraries/SharesMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {FixedPointMathLib} from "./FixedPointMathLib.sol";

/// @notice Shares management library.
/// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares: https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack.
library SharesMath {
using FixedPointMathLib for uint256;

uint256 internal constant VIRTUAL_SHARES = 1e18;
uint256 internal constant VIRTUAL_ASSETS = 1;

/// @dev Calculates the value of the given assets quoted in shares, rounding down.
/// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares.
function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
}

/// @dev Calculates the value of the given shares quoted in assets, rounding down.
/// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets.
function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}

/// @dev Calculates the value of the given assets quoted in shares, rounding up.
/// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares + VIRTUAL_SHARES.
function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
}

/// @dev Calculates the value of the given shares quoted in assets, rounding up.
/// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets + VIRTUAL_SHARES.
function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}
}
6 changes: 3 additions & 3 deletions src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ pragma solidity 0.8.20;

import {IIrm} from "src/interfaces/IIrm.sol";

import {MathLib} from "src/libraries/MathLib.sol";
import {FixedPointMathLib} from "src/libraries/FixedPointMathLib.sol";
import {Id, Market, MarketLib} from "src/libraries/MarketLib.sol";

import {Blue} from "src/Blue.sol";

contract IrmMock is IIrm {
using MathLib for uint256;
using FixedPointMathLib for uint256;
using MarketLib for Market;

Blue public immutable blue;
Expand All @@ -20,7 +20,7 @@ contract IrmMock is IIrm {

function borrowRate(Market calldata market) external view returns (uint256) {
Id id = market.id();
uint256 utilization = blue.totalBorrow(id).wDiv(blue.totalSupply(id));
uint256 utilization = blue.totalBorrow(id).divWadDown(blue.totalSupply(id));

// Divide by the number of seconds in a year.
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
Expand Down
Loading

0 comments on commit 3f206ae

Please sign in to comment.