diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index c44ea75da..4334bdbf6 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -21,12 +21,12 @@ jobs: - type: "slow" fuzz-runs: 10000 max-test-rejects: 500000 - invariant-runs: 48 - invariant-depth: 2048 + invariant-runs: 0 + invariant-depth: 512 - type: "fast" fuzz-runs: 256 max-test-rejects: 65536 - invariant-runs: 16 + invariant-runs: 0 invariant-depth: 256 runs-on: ubuntu-latest @@ -45,3 +45,4 @@ jobs: FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} FOUNDRY_INVARIANT_RUNS: ${{ matrix.invariant-runs }} FOUNDRY_INVARIANT_DEPTH: ${{ matrix.invariant-depth }} + FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml new file mode 100644 index 000000000..51aaf7f53 --- /dev/null +++ b/.github/workflows/npm-release.yml @@ -0,0 +1,20 @@ +name: Publish on NPM + +on: + workflow_dispatch: + +jobs: + publish-to-npm: + name: Publish to NPM + runs-on: ubuntu-latest + environment: + name: npm + url: https://www.npmjs.com/package/@morpho-org/morpho-blue + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Publish to npm + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + yarn publish --access public diff --git a/audits/2023-09-27-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf b/audits/2023-09-27-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf deleted file mode 100644 index 3cc9eb6eb..000000000 Binary files a/audits/2023-09-27-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf and /dev/null differ diff --git a/audits/2023-10-13-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf b/audits/2023-10-13-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf new file mode 100644 index 000000000..ba2dda9da Binary files /dev/null and b/audits/2023-10-13-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf differ diff --git a/audits/2023-11-13-cantina-managed-review-draft.pdf b/audits/2023-11-13-cantina-managed-review-draft.pdf new file mode 100644 index 000000000..090d1bef0 Binary files /dev/null and b/audits/2023-11-13-cantina-managed-review-draft.pdf differ diff --git a/certora/configuration/RatioMath.conf b/certora/configuration/RatioMath.conf index ee100b758..400061d5b 100644 --- a/certora/configuration/RatioMath.conf +++ b/certora/configuration/RatioMath.conf @@ -4,7 +4,9 @@ ], "verify": "MorphoHarness:certora/specs/RatioMath.spec", "prover_args": [ - "-smt_hashingScheme plaininjectivity" + "-smt_hashingScheme plaininjectivity", + "-mediumTimeout 30", + "-timeout 3600" ], "msg": "Morpho Blue Ratio Math" } diff --git a/certora/harness/MorphoHarness.sol b/certora/harness/MorphoHarness.sol index 0f4ac6ed6..1a1912aeb 100644 --- a/certora/harness/MorphoHarness.sol +++ b/certora/harness/MorphoHarness.sol @@ -78,21 +78,14 @@ contract MorphoHarness is Morpho { marketParamsId = Id.wrap(keccak256(abi.encode(marketParams))); } - function libMulDivUp(uint256 x, uint256 y, uint256 d) public pure returns (uint256) { + function libMulDivUp(uint256 x, uint256 y, uint256 d) external pure returns (uint256) { return MathLib.mulDivUp(x, y, d); } - function libMulDivDown(uint256 x, uint256 y, uint256 d) public pure returns (uint256) { + function libMulDivDown(uint256 x, uint256 y, uint256 d) external pure returns (uint256) { return MathLib.mulDivDown(x, y, d); } - function accrueInterest(MarketParams memory marketParams) external { - Id id = marketParams.id(); - require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); - - _accrueInterest(marketParams, id); - } - function isHealthy(MarketParams memory marketParams, address user) external view returns (bool) { return _isHealthy(marketParams, marketParams.id(), user); } diff --git a/certora/harness/TransferHarness.sol b/certora/harness/TransferHarness.sol index 5e6954a46..67a6d88c8 100644 --- a/certora/harness/TransferHarness.sol +++ b/certora/harness/TransferHarness.sol @@ -13,23 +13,23 @@ interface IERC20Extended is IERC20 { contract TransferHarness { using SafeTransferLib for IERC20; - function libSafeTransferFrom(address token, address from, address to, uint256 value) public { + function libSafeTransferFrom(address token, address from, address to, uint256 value) external { IERC20(token).safeTransferFrom(from, to, value); } - function libSafeTransfer(address token, address to, uint256 value) public { + function libSafeTransfer(address token, address to, uint256 value) external { IERC20(token).safeTransfer(to, value); } - function balanceOf(address token, address user) public view returns (uint256) { + function balanceOf(address token, address user) external view returns (uint256) { return IERC20Extended(token).balanceOf(user); } - function allowance(address token, address owner, address spender) public view returns (uint256) { + function allowance(address token, address owner, address spender) external view returns (uint256) { return IERC20Extended(token).allowance(owner, spender); } - function totalSupply(address token) public view returns (uint256) { + function totalSupply(address token) external view returns (uint256) { return IERC20Extended(token).totalSupply(); } } diff --git a/certora/specs/Transfer.spec b/certora/specs/Transfer.spec index 5a92cfec0..e9861d323 100644 --- a/certora/specs/Transfer.spec +++ b/certora/specs/Transfer.spec @@ -68,7 +68,7 @@ rule transferRevertCondition(address token, address to, uint256 amount) { libSafeTransfer@withrevert(token, to, amount); - assert lastReverted == (initialBalance < amount); + assert lastReverted <=> initialBalance < amount; } // Check the revert condition of the summary of safeTransferFrom. @@ -83,5 +83,5 @@ rule transferFromRevertCondition(address token, address from, address to, uint25 libSafeTransferFrom@withrevert(token, from, to, amount); - assert lastReverted == (initialBalance < amount) || allowance < amount; + assert lastReverted <=> initialBalance < amount || allowance < amount; } diff --git a/foundry.toml b/foundry.toml index b698a4eec..d641b9154 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ via-ir = true optimizer_runs = 4294967295 [profile.default.invariant] -runs = 16 +runs = 8 depth = 256 fail_on_revert = true diff --git a/package.json b/package.json index 4c3d2c830..9f9364482 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,15 @@ { - "name": "morpho-blue", + "name": "@morpho-org/morpho-blue", "description": "Morpho Blue Protocol", "license": "BUSL-1.1", - "version": "1.0.0", + "version": "0.1.0", + "files": [ + "src", + "README.md", + "LICENSE" + ], "scripts": { - "postinstall": "husky install && forge install", + "prepare": "husky install && forge install", "build:forge": "FOUNDRY_PROFILE=build forge build", "build:hardhat": "npx hardhat compile", "test:forge": "FOUNDRY_PROFILE=test forge test", diff --git a/remappings.txt b/remappings.txt deleted file mode 100644 index 9da1c6722..000000000 --- a/remappings.txt +++ /dev/null @@ -1 +0,0 @@ -@morpho-blue/=./ diff --git a/src/Morpho.sol b/src/Morpho.sol index 0f24d1a9f..f755904b6 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -1,7 +1,16 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.19; -import {Id, IMorpho, MarketParams, Position, Market, Authorization, Signature} from "./interfaces/IMorpho.sol"; +import { + Id, + IMorphoStaticTyping, + IMorphoBase, + MarketParams, + Position, + Market, + Authorization, + Signature +} from "./interfaces/IMorpho.sol"; import { IMorphoLiquidateCallback, IMorphoRepayCallback, @@ -26,7 +35,7 @@ import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Morpho contract. -contract Morpho is IMorpho { +contract Morpho is IMorphoStaticTyping { using MathLib for uint128; using MathLib for uint256; using UtilsLib for uint256; @@ -36,39 +45,40 @@ contract Morpho is IMorpho { /* IMMUTABLES */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase bytes32 public immutable DOMAIN_SEPARATOR; /* STORAGE */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase address public owner; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase address public feeRecipient; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => mapping(address => Position)) public position; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => Market) public market; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => bool) public isIrmEnabled; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(uint256 => bool) public isLltvEnabled; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => mapping(address => bool)) public isAuthorized; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => uint256) public nonce; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => MarketParams) public idToMarketParams; /* CONSTRUCTOR */ - /// @notice Constructs the contract. /// @param newOwner The new owner of the contract. constructor(address newOwner) { require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS); - owner = newOwner; DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this))); + owner = newOwner; + + emit EventsLib.SetOwner(newOwner); } /* MODIFIERS */ @@ -81,7 +91,7 @@ contract Morpho is IMorpho { /* ONLY OWNER FUNCTIONS */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setOwner(address newOwner) external onlyOwner { require(newOwner != owner, ErrorsLib.ALREADY_SET); @@ -90,7 +100,7 @@ contract Morpho is IMorpho { emit EventsLib.SetOwner(newOwner); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function enableIrm(address irm) external onlyOwner { require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET); @@ -99,7 +109,7 @@ contract Morpho is IMorpho { emit EventsLib.EnableIrm(irm); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function enableLltv(uint256 lltv) external onlyOwner { require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET); require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED); @@ -109,7 +119,7 @@ contract Morpho is IMorpho { emit EventsLib.EnableLltv(lltv); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); @@ -125,7 +135,7 @@ contract Morpho is IMorpho { emit EventsLib.SetFee(id, newFee); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setFeeRecipient(address newFeeRecipient) external onlyOwner { require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET); @@ -136,7 +146,7 @@ contract Morpho is IMorpho { /* MARKET CREATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function createMarket(MarketParams memory marketParams) external { Id id = marketParams.id(); require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED); @@ -152,7 +162,7 @@ contract Morpho is IMorpho { /* SUPPLY MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function supply( MarketParams memory marketParams, uint256 assets, @@ -183,7 +193,7 @@ contract Morpho is IMorpho { return (assets, shares); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function withdraw( MarketParams memory marketParams, uint256 assets, @@ -194,8 +204,8 @@ contract Morpho is IMorpho { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -218,7 +228,7 @@ contract Morpho is IMorpho { /* BORROW MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function borrow( MarketParams memory marketParams, uint256 assets, @@ -229,8 +239,8 @@ contract Morpho is IMorpho { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -252,7 +262,7 @@ contract Morpho is IMorpho { return (assets, shares); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function repay( MarketParams memory marketParams, uint256 assets, @@ -286,7 +296,7 @@ contract Morpho is IMorpho { /* COLLATERAL MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data) external { @@ -306,15 +316,15 @@ contract Morpho is IMorpho { IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(assets != 0, ErrorsLib.ZERO_ASSETS); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -330,7 +340,7 @@ contract Morpho is IMorpho { /* LIQUIDATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function liquidate( MarketParams memory marketParams, address borrower, @@ -373,13 +383,16 @@ contract Morpho is IMorpho { position[id][borrower].collateral -= seizedAssets.toUint128(); - // Realize the bad debt if needed. Note that it saves ~3k gas to do it. uint256 badDebtShares; if (position[id][borrower].collateral == 0) { badDebtShares = position[id][borrower].borrowShares; - uint256 badDebt = badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); - market[id].totalSupplyAssets -= badDebt.toUint128(); + uint256 badDebt = UtilsLib.min( + market[id].totalBorrowAssets, + badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares) + ); + market[id].totalBorrowAssets -= badDebt.toUint128(); + market[id].totalSupplyAssets -= badDebt.toUint128(); market[id].totalBorrowShares -= badDebtShares.toUint128(); position[id][borrower].borrowShares = 0; } @@ -398,7 +411,7 @@ contract Morpho is IMorpho { /* FLASH LOANS */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function flashLoan(address token, uint256 assets, bytes calldata data) external { IERC20(token).safeTransfer(msg.sender, assets); @@ -411,16 +424,16 @@ contract Morpho is IMorpho { /* AUTHORIZATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setAuthorization(address authorized, bool newIsAuthorized) external { isAuthorized[msg.sender][authorized] = newIsAuthorized; emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external { - require(block.timestamp < authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED); + require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED); require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE); bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization)); @@ -445,6 +458,14 @@ contract Morpho is IMorpho { /* INTEREST MANAGEMENT */ + /// @inheritdoc IMorphoBase + function accrueInterest(MarketParams memory marketParams) external { + Id id = marketParams.id(); + require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); + + _accrueInterest(marketParams, id); + } + /// @dev Accrues interest for the given market `marketParams`. /// @dev Assumes that the inputs `marketParams` and `id` match. function _accrueInterest(MarketParams memory marketParams, Id id) internal { @@ -452,26 +473,23 @@ contract Morpho is IMorpho { if (elapsed == 0) return; - if (market[id].totalBorrowAssets != 0) { - uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]); - uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); - market[id].totalBorrowAssets += interest.toUint128(); - market[id].totalSupplyAssets += interest.toUint128(); - - uint256 feeShares; - if (market[id].fee != 0) { - uint256 feeAmount = interest.wMulDown(market[id].fee); - // The fee amount is subtracted from the total supply in this calculation to compensate for the fact - // that total supply is already increased by the full interest (including the fee amount). - feeShares = - feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares); - position[id][feeRecipient].supplyShares += feeShares; - market[id].totalSupplyShares += feeShares.toUint128(); - } - - emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares); + uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]); + uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); + market[id].totalBorrowAssets += interest.toUint128(); + market[id].totalSupplyAssets += interest.toUint128(); + + uint256 feeShares; + if (market[id].fee != 0) { + uint256 feeAmount = interest.wMulDown(market[id].fee); + // The fee amount is subtracted from the total supply in this calculation to compensate for the fact + // that total supply is already increased by the full interest (including the fee amount). + feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares); + position[id][feeRecipient].supplyShares += feeShares; + market[id].totalSupplyShares += feeShares.toUint128(); } + emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares); + // Safe "unchecked" cast. market[id].lastUpdate = uint128(block.timestamp); } @@ -508,7 +526,7 @@ contract Morpho is IMorpho { /* STORAGE VIEW */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) { uint256 nSlots = slots.length; diff --git a/src/interfaces/IIrm.sol b/src/interfaces/IIrm.sol index 2615e3a7b..db2aaf873 100644 --- a/src/interfaces/IIrm.sol +++ b/src/interfaces/IIrm.sol @@ -9,12 +9,10 @@ import {MarketParams, Market} from "./IMorpho.sol"; /// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement. interface IIrm { /// @notice Returns the borrow rate of the market `marketParams`. - /// @param marketParams The MarketParams struct of the market. - /// @param market The Market struct of the market. + /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); /// @notice Returns the borrow rate of the market `marketParams` without modifying any storage. - /// @param marketParams The MarketParams struct of the market. - /// @param market The Market struct of the market. + /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); } diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 02aa4e204..b928e50ad 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -46,14 +46,12 @@ struct Signature { bytes32 s; } -/// @title IMorpho -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Interface of Morpho. -interface IMorpho { +/// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho. +/// @dev Consider using the IMorpho interface instead of this one. +interface IMorphoBase { /// @notice The EIP-712 domain separator. - /// @dev Warning: In case of a hardfork, every EIP-712 signed message based on this domain separator can be reused - /// on the forked chain because the domain separator would be the same. + /// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing + /// the same chain id because the domain separator would be the same. function DOMAIN_SEPARATOR() external view returns (bytes32); /// @notice The owner of the contract. @@ -66,25 +64,6 @@ interface IMorpho { /// @dev The recipient receives the fees of a given market through a supply position on that market. function feeRecipient() external view returns (address); - /// @notice The state of the position of `user` on the market corresponding to `id`. - function position(Id id, address user) - external - view - returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); - - /// @notice The state of the market corresponding to `id`. - function market(Id id) - external - view - returns ( - uint128 totalSupplyAssets, - uint128 totalSupplyShares, - uint128 totalBorrowAssets, - uint128 totalBorrowShares, - uint128 lastUpdate, - uint128 fee - ); - /// @notice Whether the `irm` is enabled. function isIrmEnabled(address irm) external view returns (bool); @@ -98,15 +77,7 @@ interface IMorpho { /// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. function nonce(address authorizer) external view returns (uint256); - /// @notice The market params corresponding to `id`. - /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer - /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. - function idToMarketParams(Id id) - external - view - returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); - - /// @notice Sets `newOwner` as owner of the contract. + /// @notice Sets `newOwner` as `owner` of the contract. /// @dev Warning: No two-step transfer ownership. /// @dev Warning: The owner can be set to the zero address. function setOwner(address newOwner) external; @@ -123,10 +94,10 @@ interface IMorpho { /// @dev Warning: The recipient can be the zero address. function setFee(MarketParams memory marketParams, uint256 newFee) external; - /// @notice Sets `newFeeRecipient` as recipient of the fee. - /// @dev Warning: The fee recipient can be set to the zero address. - /// @dev Warning: The fee to be accrued on each market won't belong to the old fee recipient after calling this - /// function. + /// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee. + /// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost. + /// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To + /// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes. function setFeeRecipient(address newFeeRecipient) external; /// @notice Creates the market `marketParams`. @@ -139,7 +110,9 @@ interface IMorpho { /// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount /// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported. /// - The IRM should not re-enter Morpho. - /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties: + /// - The oracle should return a price with the correct scaling. + /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties + /// (funds could get stuck): /// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue. /// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and /// `toSharesDown` overflow. @@ -150,6 +123,8 @@ interface IMorpho { /// `liquidate` from being used under certain market conditions. /// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or /// the computation of `assetsRepaid` in `liquidate` overflow. + /// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to + /// the point where `totalBorrowShares` is very large and borrowing overflows. function createMarket(MarketParams memory marketParams) external; /// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's @@ -157,6 +132,8 @@ interface IMorpho { /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller /// is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific amount /// of shares is given for full compatibility and precision. + /// @dev If the supply of a market gets depleted, the supply share price instantly resets to + /// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`. /// @dev Supplying a large amount can revert for overflow. /// @param marketParams The market to supply assets to. /// @param assets The amount of assets to supply. @@ -177,6 +154,8 @@ interface IMorpho { /// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow. + /// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to + /// conversion roundings between shares and assets. /// @param marketParams The market to withdraw assets from. /// @param assets The amount of assets to withdraw. /// @param shares The amount of shares to burn. @@ -196,6 +175,8 @@ interface IMorpho { /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller /// is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is given for /// full compatibility and precision. + /// @dev If the borrow of a market gets depleted, the borrow share price instantly resets to + /// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Borrowing a large amount can revert for overflow. /// @param marketParams The market to borrow assets from. @@ -217,6 +198,8 @@ interface IMorpho { /// `onMorphoReplay` function with the given `data`. /// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`. /// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow. + /// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion + /// roundings between shares and assets. /// @param marketParams The market to repay assets to. /// @param assets The amount of assets to repay. /// @param shares The amount of shares to burn. @@ -277,6 +260,10 @@ interface IMorpho { /// @notice Executes a flash loan. /// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all /// markets combined, plus donations). + /// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached: + /// - `flashFee` is zero. + /// - `maxFlashLoan` is the token's balance of this contract. + /// - The receiver of `assets` is the caller. /// @param token The token to flash loan. /// @param assets The amount of assets to flash loan. /// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback. @@ -295,6 +282,69 @@ interface IMorpho { /// @param signature The signature. function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; + /// @notice Accrues interest for the given market `marketParams`. + function accrueInterest(MarketParams memory marketParams) external; + /// @notice Returns the data stored on the different `slots`. function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory); } + +/// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler. +/// @dev Consider using the IMorpho interface instead of this one. +interface IMorphoStaticTyping is IMorphoBase { + /// @notice The state of the position of `user` on the market corresponding to `id`. + /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest + /// accrual. + function position(Id id, address user) + external + view + returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); + + /// @notice The state of the market corresponding to `id`. + /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest + /// accrual. + function market(Id id) + external + view + returns ( + uint128 totalSupplyAssets, + uint128 totalSupplyShares, + uint128 totalBorrowAssets, + uint128 totalBorrowShares, + uint128 lastUpdate, + uint128 fee + ); + + /// @notice The market params corresponding to `id`. + /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer + /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. + function idToMarketParams(Id id) + external + view + returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); +} + +/// @title IMorpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures. +interface IMorpho is IMorphoBase { + /// @notice The state of the position of `user` on the market corresponding to `id`. + /// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest + /// accrual. + function position(Id id, address user) external view returns (Position memory p); + + /// @notice The state of the market corresponding to `id`. + /// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last + /// interest accrual. + function market(Id id) external view returns (Market memory m); + + /// @notice The market params corresponding to `id`. + /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer + /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. + function idToMarketParams(Id id) external view returns (MarketParams memory); +} diff --git a/src/interfaces/IOracle.sol b/src/interfaces/IOracle.sol index 576230b5b..482737ef9 100644 --- a/src/interfaces/IOracle.sol +++ b/src/interfaces/IOracle.sol @@ -5,6 +5,7 @@ pragma solidity >=0.5.0; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Interface that oracles used by Morpho must implement. +/// @dev It is the user's responsibility to select markets with safe oracles. interface IOracle { /// @notice Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36. /// @dev It corresponds to the price of 10**(collateral token decimals) assets of collateral token quoted in diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index 2ec633055..653db4f87 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -8,33 +8,33 @@ uint256 constant WAD = 1e18; /// @custom:contact security@morpho.org /// @notice Library to manage fixed-point arithmetic. library MathLib { - /// @dev (x * y) / WAD rounded down. + /// @dev Returns (`x` * `y`) / `WAD` rounded down. function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); } - /// @dev (x * WAD) / y rounded down. + /// @dev Returns (`x` * `WAD`) / `y` rounded down. function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); } - /// @dev (x * WAD) / y rounded up. + /// @dev Returns (`x` * `WAD`) / `y` rounded up. function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); } - /// @dev (x * y) / d rounded down. + /// @dev Returns (`x` * `y`) / `d` rounded down. function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y) / d; } - /// @dev (x * y) / d rounded up. + /// @dev Returns (`x` * `y`) / `d` rounded up. function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y + (d - 1)) / d; } - /// @dev The sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a continuous - /// compound interest rate. + /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a + /// continuous compound interest rate. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { uint256 firstTerm = x * n; uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD); diff --git a/src/libraries/periphery/MorphoBalancesLib.sol b/src/libraries/periphery/MorphoBalancesLib.sol index 9f9fbd693..3afabfd5f 100644 --- a/src/libraries/periphery/MorphoBalancesLib.sol +++ b/src/libraries/periphery/MorphoBalancesLib.sol @@ -10,10 +10,6 @@ import {MorphoLib} from "./MorphoLib.sol"; import {SharesMathLib} from "../SharesMathLib.sol"; import {MarketParamsLib} from "../MarketParamsLib.sol"; -interface IMorphoMarketStruct { - function market(Id id) external view returns (Market memory); -} - /// @title MorphoBalancesLib /// @author Morpho Labs /// @custom:contact security@morpho.org @@ -41,10 +37,11 @@ library MorphoBalancesLib { { Id id = marketParams.id(); - Market memory market = IMorphoMarketStruct(address(morpho)).market(id); + Market memory market = morpho.market(id); uint256 elapsed = block.timestamp - market.lastUpdate; + // Skipped if elapsed == 0 of if totalBorrowAssets == 0 because interest would be null. if (elapsed != 0 && market.totalBorrowAssets != 0) { uint256 borrowRate = IIrm(marketParams.irm).borrowRateView(marketParams, market); uint256 interest = market.totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); @@ -65,7 +62,7 @@ library MorphoBalancesLib { } /// @notice Returns the expected total supply assets of a market after having accrued interest. - function expectedTotalSupply(IMorpho morpho, MarketParams memory marketParams) + function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyAssets) @@ -74,7 +71,7 @@ library MorphoBalancesLib { } /// @notice Returns the expected total borrow assets of a market after having accrued interest. - function expectedTotalBorrow(IMorpho morpho, MarketParams memory marketParams) + function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalBorrowAssets) @@ -91,9 +88,11 @@ library MorphoBalancesLib { (, totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); } - /// @notice Returns the expected supply balance of a user on a market after having accrued interest. + /// @notice Returns the expected supply assets balance of `user` on a market after having accrued interest. /// @dev Warning: Wrong for `feeRecipient` because their supply shares increase is not taken into account. - function expectedSupplyBalance(IMorpho morpho, MarketParams memory marketParams, address user) + /// @dev Warning: Withdrawing a supply position using the expected assets balance can lead to a revert due to + /// conversion roundings between shares and assets. + function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) @@ -105,8 +104,10 @@ library MorphoBalancesLib { return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares); } - /// @notice Returns the expected borrow balance of a user on a market after having accrued interest. - function expectedBorrowBalance(IMorpho morpho, MarketParams memory marketParams, address user) + /// @notice Returns the expected borrow assets balance of `user` on a market after having accrued interest. + /// @dev Warning: repaying a borrow position using the expected assets balance can lead to a revert due to + /// conversion roundings between shares and assets. + function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) diff --git a/src/libraries/periphery/MorphoLib.sol b/src/libraries/periphery/MorphoLib.sol index de82e6d7c..c366d1a6b 100644 --- a/src/libraries/periphery/MorphoLib.sol +++ b/src/libraries/periphery/MorphoLib.sol @@ -8,6 +8,7 @@ import {MorphoStorageLib} from "./MorphoStorageLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Helper library to access Morpho storage variables. +/// @dev Warning: Supply and borrow getters may return outdated values that do not include accrued interest. library MorphoLib { function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.positionSupplySharesSlot(id, user)); diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol index e72197725..114d0bc27 100644 --- a/src/mocks/IrmMock.sol +++ b/src/mocks/IrmMock.sol @@ -10,6 +10,8 @@ contract IrmMock is IIrm { using MathLib for uint128; function borrowRateView(MarketParams memory, Market memory market) public pure returns (uint256) { + if (market.totalSupplyAssets == 0) return 0; + uint256 utilization = market.totalBorrowAssets.wDivDown(market.totalSupplyAssets); // Divide by the number of seconds in a year. diff --git a/test/forge/BaseTest.sol b/test/forge/BaseTest.sol index 3a59787a0..a26dab28e 100644 --- a/test/forge/BaseTest.sol +++ b/test/forge/BaseTest.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "../../lib/forge-std/src/Test.sol"; import "../../lib/forge-std/src/console.sol"; +import {IMorpho} from "../../src/interfaces/IMorpho.sol"; import "../../src/interfaces/IMorphoCallbacks.sol"; import {IrmMock} from "../../src/mocks/IrmMock.sol"; import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol"; @@ -66,7 +67,7 @@ contract BaseTest is Test { OWNER = makeAddr("Owner"); FEE_RECIPIENT = makeAddr("FeeRecipient"); - morpho = new Morpho(OWNER); + morpho = IMorpho(address(new Morpho(OWNER))); loanToken = new ERC20Mock(); vm.label(address(loanToken), "LoanToken"); @@ -222,7 +223,7 @@ contract BaseTest is Test { uint256 collateral = morpho.collateral(_id, onBehalf); uint256 collateralPrice = IOracle(_marketParams.oracle).price(); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, onBehalf); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, onBehalf); return bound( assets, @@ -236,7 +237,7 @@ contract BaseTest is Test { view returns (uint256) { - uint256 supplyBalance = morpho.expectedSupplyBalance(_marketParams, onBehalf); + uint256 supplyBalance = morpho.expectedSupplyAssets(_marketParams, onBehalf); return bound(assets, 0, MAX_TEST_AMOUNT.zeroFloorSub(supplyBalance)); } @@ -266,7 +267,7 @@ contract BaseTest is Test { { Id _id = _marketParams.id(); - uint256 supplyBalance = morpho.expectedSupplyBalance(_marketParams, onBehalf); + uint256 supplyBalance = morpho.expectedSupplyAssets(_marketParams, onBehalf); uint256 liquidity = morpho.totalSupplyAssets(_id) - morpho.totalBorrowAssets(_id); return bound(assets, 0, MAX_TEST_AMOUNT.min(supplyBalance).min(liquidity)); @@ -296,7 +297,7 @@ contract BaseTest is Test { Id _id = _marketParams.id(); uint256 maxBorrow = _maxBorrow(_marketParams, onBehalf); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, onBehalf); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, onBehalf); uint256 liquidity = morpho.totalSupplyAssets(_id) - morpho.totalBorrowAssets(_id); return bound(assets, 0, MAX_TEST_AMOUNT.min(maxBorrow - borrowed).min(liquidity)); @@ -336,7 +337,7 @@ contract BaseTest is Test { uint256 collateral = morpho.collateral(_id, borrower); uint256 collateralPrice = IOracle(_marketParams.oracle).price(); - uint256 maxRepaidAssets = morpho.expectedBorrowBalance(_marketParams, borrower); + uint256 maxRepaidAssets = morpho.expectedBorrowAssets(_marketParams, borrower); uint256 maxSeizedAssets = maxRepaidAssets.wMulDown(_liquidationIncentiveFactor(_marketParams.lltv)).mulDivDown( ORACLE_PRICE_SCALE, collateralPrice ); @@ -372,7 +373,7 @@ contract BaseTest is Test { function _isHealthy(MarketParams memory _marketParams, address user) internal view returns (bool) { uint256 maxBorrow = _maxBorrow(_marketParams, user); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, user); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, user); return maxBorrow >= borrowed; } @@ -385,12 +386,6 @@ contract BaseTest is Test { return bound(lltv, 0, WAD - 1); } - function _accrueInterest(MarketParams memory market) internal { - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(market, 1, address(this), hex""); - morpho.withdrawCollateral(market, 1, address(this), address(10)); - } - function neq(MarketParams memory a, MarketParams memory b) internal pure returns (bool) { return (Id.unwrap(a.id()) != Id.unwrap(b.id())); } diff --git a/test/forge/integration/AccrueInterestIntegrationTest.sol b/test/forge/integration/AccrueInterestIntegrationTest.sol index 25b91098b..3b91564f7 100644 --- a/test/forge/integration/AccrueInterestIntegrationTest.sol +++ b/test/forge/integration/AccrueInterestIntegrationTest.sol @@ -8,6 +8,13 @@ contract AccrueInterestIntegrationTest is BaseTest { using MorphoLib for IMorpho; using SharesMathLib for uint256; + function testAccrueInterestMarketNotCreated(MarketParams memory marketParamsFuzz) public { + vm.assume(neq(marketParamsFuzz, marketParams)); + + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); + morpho.accrueInterest(marketParamsFuzz); + } + function testAccrueInterestNoTimeElapsed(uint256 amountSupplied, uint256 amountBorrowed) public { uint256 collateralPrice = oracle.price(); uint256 amountCollateral; @@ -28,7 +35,7 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id); uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply"); @@ -49,7 +56,7 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id); uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply"); @@ -86,12 +93,9 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 expectedAccruedInterest = totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(blocks * BLOCK_TIME)); - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(marketParams, 1, address(this), hex""); vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.AccrueInterest(id, borrowRate, expectedAccruedInterest, 0); - // Accrues interest. - morpho.withdrawCollateral(marketParams, 1, address(this), address(this)); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued + expectedAccruedInterest, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued + expectedAccruedInterest, "total supply"); @@ -151,12 +155,9 @@ contract AccrueInterestIntegrationTest is BaseTest { params.totalSupplySharesBeforeAccrued ); - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(marketParams, 1, address(this), hex""); vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.AccrueInterest(id, params.borrowRate, params.expectedAccruedInterest, params.feeShares); - // Accrues interest. - morpho.withdrawCollateral(marketParams, 1, address(this), address(this)); + morpho.accrueInterest(marketParams); assertEq( morpho.totalSupplyAssets(id), diff --git a/test/forge/integration/AuthorizationIntegrationTest.sol b/test/forge/integration/AuthorizationIntegrationTest.sol index d8cb5b178..263eaa550 100644 --- a/test/forge/integration/AuthorizationIntegrationTest.sol +++ b/test/forge/integration/AuthorizationIntegrationTest.sol @@ -22,7 +22,7 @@ contract AuthorizationIntegrationTest is BaseTest { uint256 blocks ) public { blocks = _boundBlocks(blocks); - authorization.deadline = block.timestamp; + authorization.deadline = block.timestamp - 1; // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -40,7 +40,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSigWrongPK(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -55,7 +55,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSigWrongNonce(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); authorization.nonce = bound(authorization.nonce, 1, type(uint256).max); // Private key must be less than the secp256k1 curve order. @@ -71,7 +71,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSig(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -89,7 +89,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationFailsWithReusedSig(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); diff --git a/test/forge/integration/CreateMarketIntegrationTest.sol b/test/forge/integration/CreateMarketIntegrationTest.sol index f0f31822b..1fd2eb083 100644 --- a/test/forge/integration/CreateMarketIntegrationTest.sol +++ b/test/forge/integration/CreateMarketIntegrationTest.sol @@ -84,13 +84,12 @@ contract CreateMarketIntegrationTest is BaseTest { morpho.createMarket(marketParamsFuzz); vm.stopPrank(); - (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) = - morpho.idToMarketParams(marketParamsFuzzId); - - assertEq(marketParamsFuzz.loanToken, loanToken, "loanToken != loanToken"); - assertEq(marketParamsFuzz.collateralToken, collateralToken, "collateralToken != collateralToken"); - assertEq(marketParamsFuzz.oracle, oracle, "oracle != oracle"); - assertEq(marketParamsFuzz.irm, irm, "irm != irm"); - assertEq(marketParamsFuzz.lltv, lltv, "lltv != lltv"); + MarketParams memory params = morpho.idToMarketParams(marketParamsFuzzId); + + assertEq(marketParamsFuzz.loanToken, params.loanToken, "loanToken != loanToken"); + assertEq(marketParamsFuzz.collateralToken, params.collateralToken, "collateralToken != collateralToken"); + assertEq(marketParamsFuzz.oracle, params.oracle, "oracle != oracle"); + assertEq(marketParamsFuzz.irm, params.irm, "irm != irm"); + assertEq(marketParamsFuzz.lltv, params.lltv, "lltv != lltv"); } } diff --git a/test/forge/integration/LiquidateIntegrationTest.sol b/test/forge/integration/LiquidateIntegrationTest.sol index 9bd12bc3e..f9227b635 100644 --- a/test/forge/integration/LiquidateIntegrationTest.sol +++ b/test/forge/integration/LiquidateIntegrationTest.sol @@ -66,110 +66,113 @@ contract LiquidateIntegrationTest is BaseTest { morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex""); } - function testLiquidateSeizedInputNoBadDebt( - uint256 amountCollateral, - uint256 amountSupplied, - uint256 amountBorrowed, - uint256 amountSeized, - uint256 priceCollateral, - uint256 lltv - ) public { - _setLltv(_boundTestLltv(lltv)); - (amountCollateral, amountBorrowed, priceCollateral) = - _boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral); + struct LiquidateTestParams { + uint256 amountCollateral; + uint256 amountSupplied; + uint256 amountBorrowed; + uint256 priceCollateral; + uint256 lltv; + } - vm.assume(amountCollateral > 1); + function testLiquidateSeizedInputNoBadDebtRealized(LiquidateTestParams memory params, uint256 amountSeized) + public + { + _setLltv(_boundTestLltv(params.lltv)); + (params.amountCollateral, params.amountBorrowed, params.priceCollateral) = + _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral); - amountSupplied = bound(amountSupplied, amountBorrowed, amountBorrowed + MAX_TEST_AMOUNT); - _supply(amountSupplied); + vm.assume(params.amountCollateral > 1); - uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv); - uint256 maxSeized = - amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral); - vm.assume(maxSeized != 0); - amountSeized = bound(amountSeized, 1, Math.min(maxSeized, amountCollateral - 1)); - uint256 expectedRepaid = - amountSeized.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); + params.amountSupplied = + bound(params.amountSupplied, params.amountBorrowed, params.amountBorrowed + MAX_TEST_AMOUNT); + _supply(params.amountSupplied); - loanToken.setBalance(LIQUIDATOR, amountBorrowed); - collateralToken.setBalance(BORROWER, amountCollateral); + collateralToken.setBalance(BORROWER, params.amountCollateral); - oracle.setPrice(type(uint256).max / amountCollateral); + oracle.setPrice(type(uint256).max / params.amountCollateral); vm.startPrank(BORROWER); - morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); - morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); + morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex""); + morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER); vm.stopPrank(); - oracle.setPrice(priceCollateral); + oracle.setPrice(params.priceCollateral); + + uint256 borrowShares = morpho.borrowShares(id, BORROWER); + uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv); + uint256 maxSeized = params.amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown( + ORACLE_PRICE_SCALE, params.priceCollateral + ); + vm.assume(maxSeized != 0); + + amountSeized = bound(amountSeized, 1, Math.min(maxSeized, params.amountCollateral - 1)); + uint256 expectedRepaid = + amountSeized.mulDivUp(params.priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); uint256 expectedRepaidShares = expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); + loanToken.setBalance(LIQUIDATOR, params.amountBorrowed); + vm.prank(LIQUIDATOR); vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0); (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex""); - uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - expectedRepaidShares; + uint256 expectedCollateral = params.amountCollateral - amountSeized; + uint256 expectedBorrowed = params.amountBorrowed - expectedRepaid; + uint256 expectedBorrowShares = borrowShares - expectedRepaidShares; assertEq(returnSeized, amountSeized, "returned seized amount"); assertEq(returnRepaid, expectedRepaid, "returned asset amount"); assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares"); - assertEq(morpho.totalBorrowAssets(id), amountBorrowed - expectedRepaid, "total borrow"); + assertEq(morpho.totalBorrowAssets(id), expectedBorrowed, "total borrow"); assertEq(morpho.totalBorrowShares(id), expectedBorrowShares, "total borrow shares"); - assertEq(morpho.collateral(id, BORROWER), amountCollateral - amountSeized, "collateral"); - assertEq(loanToken.balanceOf(BORROWER), amountBorrowed, "borrower balance"); - assertEq(loanToken.balanceOf(LIQUIDATOR), amountBorrowed - expectedRepaid, "liquidator balance"); - assertEq( - loanToken.balanceOf(address(morpho)), amountSupplied - amountBorrowed + expectedRepaid, "morpho balance" - ); - assertEq( - collateralToken.balanceOf(address(morpho)), amountCollateral - amountSeized, "morpho collateral balance" - ); + assertEq(morpho.collateral(id, BORROWER), expectedCollateral, "collateral"); + assertEq(loanToken.balanceOf(BORROWER), params.amountBorrowed, "borrower balance"); + assertEq(loanToken.balanceOf(LIQUIDATOR), expectedBorrowed, "liquidator balance"); + assertEq(loanToken.balanceOf(address(morpho)), params.amountSupplied - expectedBorrowed, "morpho balance"); + assertEq(collateralToken.balanceOf(address(morpho)), expectedCollateral, "morpho collateral balance"); assertEq(collateralToken.balanceOf(LIQUIDATOR), amountSeized, "liquidator collateral balance"); } - function testLiquidateSharesInputNoBadDebt( - uint256 amountCollateral, - uint256 amountSupplied, - uint256 amountBorrowed, - uint256 sharesRepaid, - uint256 priceCollateral, - uint256 lltv - ) public { - _setLltv(_boundTestLltv(lltv)); - (amountCollateral, amountBorrowed, priceCollateral) = - _boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral); - - vm.assume(amountCollateral > 1); + function testLiquidateSharesInputNoBadDebtRealized(LiquidateTestParams memory params, uint256 sharesRepaid) + public + { + _setLltv(_boundTestLltv(params.lltv)); + (params.amountCollateral, params.amountBorrowed, params.priceCollateral) = + _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral); - amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); - _supply(amountSupplied); + vm.assume(params.amountCollateral >= 1); - uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0); - uint256 maxRepaidShares = amountCollateral.mulDivDown(priceCollateral, ORACLE_PRICE_SCALE).wDivDown( - _liquidationIncentiveFactor(marketParams.lltv) - ); - vm.assume(maxRepaidShares != 0); - sharesRepaid = bound(sharesRepaid, 1, Math.min(maxRepaidShares, expectedBorrowShares)); - uint256 expectedRepaid = sharesRepaid.toAssetsUp(amountBorrowed, expectedBorrowShares); - uint256 expectedSeized = expectedRepaid.wMulDown(_liquidationIncentiveFactor(marketParams.lltv)).mulDivDown( - ORACLE_PRICE_SCALE, priceCollateral - ); + params.amountSupplied = bound(params.amountSupplied, params.amountBorrowed, MAX_TEST_AMOUNT); + _supply(params.amountSupplied); - loanToken.setBalance(LIQUIDATOR, amountBorrowed); - collateralToken.setBalance(BORROWER, amountCollateral); + collateralToken.setBalance(BORROWER, params.amountCollateral); - oracle.setPrice(type(uint256).max / amountCollateral); + oracle.setPrice(type(uint256).max / params.amountCollateral); vm.startPrank(BORROWER); - morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); - morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); + morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex""); + morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER); vm.stopPrank(); - oracle.setPrice(priceCollateral); + oracle.setPrice(params.priceCollateral); + + uint256 borrowShares = morpho.borrowShares(id, BORROWER); + uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv); + uint256 maxSharesRepaid = (params.amountCollateral - 1).mulDivDown(params.priceCollateral, ORACLE_PRICE_SCALE) + .wDivDown(liquidationIncentiveFactor).toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); + vm.assume(maxSharesRepaid != 0); + + sharesRepaid = bound(sharesRepaid, 1, Math.min(borrowShares, maxSharesRepaid)); + + uint256 expectedRepaid = sharesRepaid.toAssetsUp(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); + uint256 expectedSeized = + expectedRepaid.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, params.priceCollateral); + + loanToken.setBalance(LIQUIDATOR, params.amountBorrowed); vm.prank(LIQUIDATOR); @@ -177,22 +180,20 @@ contract LiquidateIntegrationTest is BaseTest { emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, sharesRepaid, expectedSeized, 0); (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, 0, sharesRepaid, hex""); - expectedBorrowShares = amountBorrowed.toSharesUp(0, 0) - sharesRepaid; + uint256 expectedCollateral = params.amountCollateral - expectedSeized; + uint256 expectedBorrowed = params.amountBorrowed - expectedRepaid; + uint256 expectedBorrowShares = borrowShares - sharesRepaid; assertEq(returnSeized, expectedSeized, "returned seized amount"); assertEq(returnRepaid, expectedRepaid, "returned asset amount"); assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares"); - assertEq(morpho.totalBorrowAssets(id), amountBorrowed - expectedRepaid, "total borrow"); + assertEq(morpho.totalBorrowAssets(id), expectedBorrowed, "total borrow"); assertEq(morpho.totalBorrowShares(id), expectedBorrowShares, "total borrow shares"); - assertEq(morpho.collateral(id, BORROWER), amountCollateral - expectedSeized, "collateral"); - assertEq(loanToken.balanceOf(BORROWER), amountBorrowed, "borrower balance"); - assertEq(loanToken.balanceOf(LIQUIDATOR), amountBorrowed - expectedRepaid, "liquidator balance"); - assertEq( - loanToken.balanceOf(address(morpho)), amountSupplied - amountBorrowed + expectedRepaid, "morpho balance" - ); - assertEq( - collateralToken.balanceOf(address(morpho)), amountCollateral - expectedSeized, "morpho collateral balance" - ); + assertEq(morpho.collateral(id, BORROWER), expectedCollateral, "collateral"); + assertEq(loanToken.balanceOf(BORROWER), params.amountBorrowed, "borrower balance"); + assertEq(loanToken.balanceOf(LIQUIDATOR), expectedBorrowed, "liquidator balance"); + assertEq(loanToken.balanceOf(address(morpho)), params.amountSupplied - expectedBorrowed, "morpho balance"); + assertEq(collateralToken.balanceOf(address(morpho)), expectedCollateral, "morpho collateral balance"); assertEq(collateralToken.balanceOf(LIQUIDATOR), expectedSeized, "liquidator collateral balance"); } @@ -207,7 +208,7 @@ contract LiquidateIntegrationTest is BaseTest { uint256 expectedBadDebt; } - function testLiquidateBadDebt( + function testLiquidateBadDebtRealized( uint256 amountCollateral, uint256 amountSupplied, uint256 amountBorrowed, @@ -295,4 +296,24 @@ contract LiquidateIntegrationTest is BaseTest { morpho.totalSupplyAssets(id), params.totalSupplyBeforeLiquidation - params.expectedBadDebt, "total supply" ); } + + function testBadDebtOverTotalBorrowAssets() public { + uint256 collateralAmount = 10 ether; + uint256 loanAmount = 1 ether; + _supply(loanAmount); + + collateralToken.setBalance(BORROWER, collateralAmount); + vm.startPrank(BORROWER); + morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); + morpho.borrow(marketParams, loanAmount, 0, BORROWER, BORROWER); + // Trick to inflate shares, so that the computed bad debt is greater than the total debt of the market. + morpho.borrow(marketParams, 0, 1, BORROWER, BORROWER); + vm.stopPrank(); + + oracle.setPrice(1e36 / 100); + + loanToken.setBalance(LIQUIDATOR, loanAmount); + vm.prank(LIQUIDATOR); + morpho.liquidate(marketParams, BORROWER, collateralAmount, 0, hex""); + } } diff --git a/test/forge/integration/OnlyOwnerIntegrationTest.sol b/test/forge/integration/OnlyOwnerIntegrationTest.sol index aa2586f51..fb7f8c016 100644 --- a/test/forge/integration/OnlyOwnerIntegrationTest.sol +++ b/test/forge/integration/OnlyOwnerIntegrationTest.sol @@ -12,6 +12,12 @@ contract OnlyOwnerIntegrationTest is BaseTest { new Morpho(address(0)); } + function testDeployEmitOwner() public { + vm.expectEmit(); + emit EventsLib.SetOwner(OWNER); + new Morpho(OWNER); + } + function testSetOwnerWhenNotOwner(address addressFuzz) public { vm.assume(addressFuzz != OWNER); diff --git a/test/forge/libraries/periphery/MorphoBalancesLibTest.sol b/test/forge/libraries/periphery/MorphoBalancesLibTest.sol index 516bb0847..fca8b9c64 100644 --- a/test/forge/libraries/periphery/MorphoBalancesLibTest.sol +++ b/test/forge/libraries/periphery/MorphoBalancesLibTest.sol @@ -21,7 +21,7 @@ contract MorphoBalancesLibTest is BaseTest { uint256 virtualTotalBorrowShares ) = morpho.expectedMarketBalances(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(virtualTotalSupplyAssets, morpho.totalSupplyAssets(id), "total supply assets"); assertEq(virtualTotalBorrowAssets, morpho.totalBorrowAssets(id), "total borrow assets"); @@ -34,11 +34,11 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedTotalSupply = morpho.expectedTotalSupply(marketParams); + uint256 expectedTotalSupplyAssets = morpho.expectedTotalSupplyAssets(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); - assertEq(expectedTotalSupply, morpho.totalSupplyAssets(id)); + assertEq(expectedTotalSupplyAssets, morpho.totalSupplyAssets(id)); } function testExpectedTotalBorrow(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed, uint256 fee) @@ -46,11 +46,11 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedTotalBorrow = morpho.expectedTotalBorrow(marketParams); + uint256 expectedTotalBorrowAssets = morpho.expectedTotalBorrowAssets(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); - assertEq(expectedTotalBorrow, morpho.totalBorrowAssets(id)); + assertEq(expectedTotalBorrowAssets, morpho.totalBorrowAssets(id)); } function testExpectedTotalSupplyShares( @@ -63,19 +63,19 @@ contract MorphoBalancesLibTest is BaseTest { uint256 expectedTotalSupplyShares = morpho.expectedTotalSupplyShares(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(expectedTotalSupplyShares, morpho.totalSupplyShares(id)); } function testExpectedSupplyBalance(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed, uint256 fee) - internal + public { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedSupplyBalance = morpho.expectedSupplyBalance(marketParams, address(this)); + uint256 expectedSupplyBalance = morpho.expectedSupplyAssets(marketParams, address(this)); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); uint256 actualSupplyBalance = morpho.supplyShares(id, address(this)).toAssetsDown( morpho.totalSupplyAssets(id), morpho.totalSupplyShares(id) @@ -85,13 +85,13 @@ contract MorphoBalancesLibTest is BaseTest { } function testExpectedBorrowBalance(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed, uint256 fee) - internal + public { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedBorrowBalance = morpho.expectedBorrowBalance(marketParams, address(this)); + uint256 expectedBorrowBalance = morpho.expectedBorrowAssets(marketParams, address(this)); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); uint256 actualBorrowBalance = morpho.borrowShares(id, address(this)).toAssetsUp( morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id) diff --git a/test/forge/libraries/periphery/MorphoLibTest.sol b/test/forge/libraries/periphery/MorphoLibTest.sol index 864bee16b..8ff015d4a 100644 --- a/test/forge/libraries/periphery/MorphoLibTest.sol +++ b/test/forge/libraries/periphery/MorphoLibTest.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {MorphoBalancesLib} from "../../../../src/libraries/periphery/MorphoBalancesLib.sol"; - import "../../BaseTest.sol"; contract MorphoLibTest is BaseTest { @@ -50,21 +48,21 @@ contract MorphoLibTest is BaseTest { function testSupplyShares(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (uint256 expectedSupplyShares,,) = morpho.position(id, address(this)); + uint256 expectedSupplyShares = morpho.position(id, address(this)).supplyShares; assertEq(morpho.supplyShares(id, address(this)), expectedSupplyShares); } function testBorrowShares(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (, uint256 expectedBorrowShares,) = morpho.position(id, BORROWER); + uint256 expectedBorrowShares = morpho.position(id, BORROWER).borrowShares; assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares); } function testCollateral(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,, uint256 expectedCollateral) = morpho.position(id, BORROWER); + uint256 expectedCollateral = morpho.position(id, BORROWER).collateral; assertEq(morpho.collateral(id, BORROWER), expectedCollateral); } @@ -73,7 +71,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (uint256 expectedTotalSupplyAssets,,,,,) = morpho.market(id); + uint256 expectedTotalSupplyAssets = morpho.market(id).totalSupplyAssets; assertEq(morpho.totalSupplyAssets(id), expectedTotalSupplyAssets); } @@ -82,7 +80,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (, uint256 expectedTotalSupplyShares,,,,) = morpho.market(id); + uint256 expectedTotalSupplyShares = morpho.market(id).totalSupplyShares; assertEq(morpho.totalSupplyShares(id), expectedTotalSupplyShares); } @@ -91,7 +89,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,, uint256 expectedTotalBorrowAssets,,,) = morpho.market(id); + uint256 expectedTotalBorrowAssets = morpho.market(id).totalBorrowAssets; assertEq(morpho.totalBorrowAssets(id), expectedTotalBorrowAssets); } @@ -100,21 +98,21 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,, uint256 expectedTotalBorrowShares,,) = morpho.market(id); + uint256 expectedTotalBorrowShares = morpho.market(id).totalBorrowShares; assertEq(morpho.totalBorrowShares(id), expectedTotalBorrowShares); } function testLastUpdate(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,,, uint256 expectedLastUpdate,) = morpho.market(id); + uint256 expectedLastUpdate = morpho.market(id).lastUpdate; assertEq(morpho.lastUpdate(id), expectedLastUpdate); } function testFee(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,,,, uint256 expectedFee) = morpho.market(id); + uint256 expectedFee = morpho.market(id).fee; assertEq(morpho.fee(id), expectedFee); } } diff --git a/test/forge/libraries/periphery/MorphoStorageLibTest.sol b/test/forge/libraries/periphery/MorphoStorageLibTest.sol index 8f88f4de0..98a7cd3e8 100644 --- a/test/forge/libraries/periphery/MorphoStorageLibTest.sol +++ b/test/forge/libraries/periphery/MorphoStorageLibTest.sol @@ -93,17 +93,12 @@ contract MorphoStorageLibTest is BaseTest { assertEq(abi.decode(abi.encode(values[9]), (bool)), morpho.isAuthorized(authorizer, BORROWER)); assertEq(uint256(values[10]), morpho.nonce(authorizer)); - ( - address expectedloanToken, - address expectedCollateralToken, - address expectedOracle, - address expectedIrm, - uint256 expectedLltv - ) = morpho.idToMarketParams(id); - assertEq(abi.decode(abi.encode(values[11]), (address)), expectedloanToken); - assertEq(abi.decode(abi.encode(values[12]), (address)), expectedCollateralToken); - assertEq(abi.decode(abi.encode(values[13]), (address)), expectedOracle); - assertEq(abi.decode(abi.encode(values[14]), (address)), expectedIrm); - assertEq(uint256(values[15]), expectedLltv); + MarketParams memory expectedParams = morpho.idToMarketParams(id); + + assertEq(abi.decode(abi.encode(values[11]), (address)), expectedParams.loanToken); + assertEq(abi.decode(abi.encode(values[12]), (address)), expectedParams.collateralToken); + assertEq(abi.decode(abi.encode(values[13]), (address)), expectedParams.oracle); + assertEq(abi.decode(abi.encode(values[14]), (address)), expectedParams.irm); + assertEq(uint256(values[15]), expectedParams.lltv); } } diff --git a/test/morpho_tests.tree b/test/morpho_tests.tree index dff911e85..a2faa9950 100644 --- a/test/morpho_tests.tree +++ b/test/morpho_tests.tree @@ -242,9 +242,9 @@ └── it should transfer assets of token from the sender to Morpho . └── setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external - ├── when block.timestamp >= authorization.deadline + ├── when block.timestamp > authorization.deadline │ └── revert with SIGNATURE_EXPIRED - └── when block.timestamp < deadline + └── when block.timestamp <= deadline ├── when authorization.nonce != nonce[authorization.authorizer] │ └── revert with INVALID_NONCE └── when authorization.nonce == nonce[authorization.authorizer] diff --git a/tsconfig.json b/tsconfig.json index b301bff05..7d4c1978e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "target": "esnext", - "strict": true, - "esModuleInterop": true, - "rootDir": ".", - "baseUrl": ".", - "outDir": "dist", - "moduleResolution": "nodenext", - "resolveJsonModule": true, - "declaration": true - }, - "include": ["types", "test/hardhat"], - "files": ["./hardhat.config.ts"] + "compilerOptions": { + "target": "es6", + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist", + "baseUrl": ".", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "declaration": true + }, + "include": ["types", "test/hardhat"], + "files": ["hardhat.config.ts"] }