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

Custom libs #280

Merged
merged 19 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
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
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at fd81a9
1 change: 0 additions & 1 deletion lib/solmate
Submodule solmate deleted from bfc9c2
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
solmate/=lib/solmate/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
4 changes: 2 additions & 2 deletions src/Morpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {UtilsLib} from "./libraries/UtilsLib.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {MarketLib} from "./libraries/MarketLib.sol";
import {MathLib, WAD} from "./libraries/MathLib.sol";
import {SharesMathLib} from "./libraries/SharesMathLib.sol";
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
import {FixedPointMathLib, WAD} from "./libraries/FixedPointMathLib.sol";

/// @dev The maximum fee a market can have (25%).
uint256 constant MAX_FEE = 0.25e18;
Expand All @@ -35,7 +35,7 @@ contract Morpho is IMorpho {
using MarketLib for Market;
using SharesMathLib for uint256;
using SafeTransferLib for IERC20;
using FixedPointMathLib for uint256;
using MathLib for uint256;

/* IMMUTABLES */

Expand Down
6 changes: 4 additions & 2 deletions src/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ pragma solidity >=0.5.0;
/// @title IERC20
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @dev Empty because we only call functions in assembly. It prevents calling transfer (transferFrom) instead of safeTransfer (safeTransferFrom).
interface IERC20 {}
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
10 changes: 10 additions & 0 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/// @title ErrorsLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library exposing error messages.
library ErrorsLib {
/// @notice Thrown when the caller is not the owner.
string internal constant NOT_OWNER = "not owner";
Expand Down Expand Up @@ -49,4 +53,10 @@ library ErrorsLib {

/// @notice Thrown when the authorization signature is expired.
string internal constant SIGNATURE_EXPIRED = "signature expired";

/// @notice Thrown when a token transfer has failed.
string internal constant TRANSFER_FAILED = "transfer failed";

/// @notice Thrown when a token transferFrom has failed.
string internal constant TRANSFER_FROM_FAILED = "transferFrom failed";
}
4 changes: 4 additions & 0 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ pragma solidity ^0.8.0;

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

/// @title EventsLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library exposing events.
library EventsLib {
/// @notice Emitted when setting a new owner.
/// @param newOwner The new owner of the contract.
Expand Down
55 changes: 29 additions & 26 deletions src/libraries/FixedPointMathLib.sol → src/libraries/MathLib.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

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

uint256 constant WAD = 1e18;

/// @notice Fixed-point arithmetic library.
/// @dev Greatly inspired by Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// and by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/// @title MathLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library to manage fixed-point arithmetic.
/// @dev Inspired by https://github.com/morpho-org/morpho-utils.
library MathLib {
uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;

/// @dev (x * y) / WAD rounded down.
Expand All @@ -31,38 +31,41 @@ library FixedPointMathLib {
return mulDivUp(x, WAD, y);
}

/// @dev The sum of the last three terms in a four term taylor series expansion
/// to approximate a continuous compound interest rate: e^(nx) - 1.
function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
uint256 firstTerm = x * n;
uint256 secondTerm = wMulDown(firstTerm, firstTerm) / 2;
uint256 thirdTerm = wMulDown(secondTerm, firstTerm) / 3;

return firstTerm + secondTerm + thirdTerm;
}

/// @dev (x * y) / denominator rounded down.
function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
// Division by zero if denominator == 0.
// Overflow if
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
// x * y > type(uint256).max
// <=> y > 0 and x > type(uint256).max / y
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 or(mul(y, gt(x, div(MAX_UINT256, y))), iszero(denominator)) { revert(0, 0) }

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

/// @dev (x * y) / denominator rounded up.
function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
// Underflow if denominator == 0.
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
// Division by 0 if denominator == 0 (this case cannot occure since the above underflow happens before).
// Overflow if
// x * y + denominator - 1 > type(uint256).max
// <=> x * y > type(uint256).max - denominator - 1
// <=> y > 0 and x > (type(uint256).max - denominator - 1) / y
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 or(mul(y, gt(x, div(sub(MAX_UINT256, sub(denominator, 1)), y))), iszero(denominator)) { 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))
z := div(add(mul(x, y), sub(denominator, 1)), denominator)
}
}

/// @dev The sum of the last three terms in a four term taylor series expansion
/// to approximate a continuous compound interest rate: e^(nx) - 1.
function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
uint256 firstTerm = x * n;
uint256 secondTerm = wMulDown(firstTerm, firstTerm) / 2;
uint256 thirdTerm = wMulDown(secondTerm, firstTerm) / 3;

return firstTerm + secondTerm + thirdTerm;
}
}
79 changes: 20 additions & 59 deletions src/libraries/SafeTransferLib.sol
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,69 +1,30 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {ErrorsLib} from "../libraries/ErrorsLib.sol";

import {IERC20} from "../interfaces/IERC20.sol";

/// @notice Safe ERC20 transfer library that gracefully handles missing return values.
/// @dev Greatly inspired by Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol).
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
/// @title SafeTransferLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library to manage tokens not fully ERC20 compliant:
/// not returning a boolean for `transfer` and `transferFrom` functions.
library SafeTransferLib {
function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
bool success;

/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)

// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}

require(success, "TRANSFER_FROM_FAILED");
function safeTransfer(IERC20 token, address to, uint256 value) internal {
(bool success, bytes memory returndata) = address(token).call(abi.encodeCall(token.transfer, (to, value)));
require(
success && address(token).code.length > 0 && (returndata.length == 0 || abi.decode(returndata, (bool))),
ErrorsLib.TRANSFER_FAILED
);
}

function safeTransfer(IERC20 token, address to, uint256 amount) internal {
bool success;

/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)

// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}

require(success, "TRANSFER_FAILED");
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
(bool success, bytes memory returndata) =
address(token).call(abi.encodeCall(token.transferFrom, (from, to, value)));
require(
success && address(token).code.length > 0 && (returndata.length == 0 || abi.decode(returndata, (bool))),
ErrorsLib.TRANSFER_FROM_FAILED
);
}
}
8 changes: 4 additions & 4 deletions src/libraries/SharesMathLib.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

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

/// @title SharesMath
/// @title SharesMathLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @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.
/// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack.
library SharesMathLib {
using FixedPointMathLib for uint256;
using MathLib for uint256;

uint256 internal constant VIRTUAL_SHARES = 1e18;

Expand Down
6 changes: 5 additions & 1 deletion src/libraries/UtilsLib.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/// @dev Inspired by morpho-utils.
/// @title UtilsLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library exposing helpers.
/// @dev Inspired by https://github.com/morpho-org/morpho-utils.
library UtilsLib {
/// @dev Returns true if there is exactly one zero.
function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
Expand Down
9 changes: 5 additions & 4 deletions src/mocks/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";

contract ERC20Mock is ERC20 {
constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {}
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function setBalance(address owner, uint256 amount) external {
balanceOf[owner] = amount;
function setBalance(address account, uint256 amount) external {
_burn(account, balanceOf(account));
_mint(account, amount);
}
}
6 changes: 2 additions & 4 deletions src/mocks/FlashBorrowerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ pragma solidity ^0.8.0;
import {IFlashLender} from "../interfaces/IFlashLender.sol";
import {IMorphoFlashLoanCallback} from "../interfaces/IMorphoCallbacks.sol";

import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";

contract FlashBorrowerMock is IMorphoFlashLoanCallback {
using SafeTransferLib for ERC20;

IFlashLender private immutable MORPHO;

constructor(IFlashLender newMorpho) {
Expand All @@ -22,6 +20,6 @@ contract FlashBorrowerMock is IMorphoFlashLoanCallback {
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
require(msg.sender == address(MORPHO));
address token = abi.decode(data, (address));
ERC20(token).safeApprove(address(MORPHO), assets);
ERC20(token).approve(address(MORPHO), assets);
}
}
4 changes: 2 additions & 2 deletions src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ pragma solidity ^0.8.0;
import {IIrm} from "../interfaces/IIrm.sol";
import {Id, Market, IMorpho} from "../interfaces/IMorpho.sol";

import {FixedPointMathLib} from "../libraries/FixedPointMathLib.sol";
import {MathLib} from "../libraries/MathLib.sol";
import {MarketLib} from "../libraries/MarketLib.sol";

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

IMorpho private immutable MORPHO;
Expand Down
2 changes: 1 addition & 1 deletion src/mocks/OracleMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.0;

import {IOracle} from "../interfaces/IOracle.sol";

import {FixedPointMathLib, WAD} from "../libraries/FixedPointMathLib.sol";
import {MathLib, WAD} from "../libraries/MathLib.sol";

contract OracleMock is IOracle {
uint256 internal _price;
Expand Down
22 changes: 0 additions & 22 deletions test/forge/Math.t.sol

This file was deleted.

Loading