Skip to content

Commit

Permalink
Merge pull request #1 from morpho-org/feat/licence
Browse files Browse the repository at this point in the history
Fixed LI-CF PreLiquidation
  • Loading branch information
peyha authored Sep 25, 2024
2 parents 22c26fe + fc5728d commit 9ae4f40
Show file tree
Hide file tree
Showing 25 changed files with 829 additions and 103 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Foundry

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

jobs:
test:
strategy:
fail-fast: true

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run forge fmt
run: forge fmt --check

- name: Run forge build
run: forge build --sizes

- name: Run forge tests
run: forge test -vvv
45 changes: 0 additions & 45 deletions .github/workflows/test.yml

This file was deleted.

6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/morpho-blue"]
path = lib/morpho-blue
url = [email protected]:morpho-org/morpho-blue.git
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
via_ir = true
optimizer_runs = 999999 # Etherscan does not support verifying contracts with more optimization runs.

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
2 changes: 1 addition & 1 deletion lib/forge-std
1 change: 1 addition & 0 deletions lib/morpho-blue
Submodule morpho-blue added at 044840
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at 97bdb2
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

164 changes: 164 additions & 0 deletions src/PreLiquidation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;

import {Id, MarketParams, IMorpho, Position, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol";
import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol";
import {UtilsLib} from "../lib/morpho-blue/src/libraries/UtilsLib.sol";
import {ORACLE_PRICE_SCALE} from "../lib/morpho-blue/src/libraries/ConstantsLib.sol";
import {WAD, MathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol";
import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol";
import {SafeTransferLib} from "../lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {IPreLiquidationCallback} from "./interfaces/IPreLiquidationCallback.sol";
import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidation.sol";
import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol";

/// @title PreLiquidation
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice The Fixed LIF, Fixed CF pre-liquidation contract for Morpho.
contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback {
using SharesMathLib for uint256;
using MathLib for uint256;
using SafeTransferLib for ERC20;

/* IMMUTABLE */

/// @notice Morpho's address.
IMorpho public immutable MORPHO;
/// @notice The id of the Morpho Market specific to the PreLiquidation contract.
Id public immutable ID;

// Market parameters
address internal immutable LOAN_TOKEN;
address internal immutable COLLATERAL_TOKEN;
address internal immutable ORACLE;
address internal immutable IRM;
uint256 internal immutable LLTV;

// Pre-liquidation parameters
uint256 internal immutable PRE_LLTV;
uint256 internal immutable CLOSE_FACTOR;
uint256 internal immutable PRE_LIQUIDATION_INCENTIVE_FACTOR;
address internal immutable PRE_LIQUIDATION_ORACLE;

/// @notice The Morpho market parameters specific to the PreLiquidation contract.
function marketParams() public view returns (MarketParams memory) {
return MarketParams({
loanToken: LOAN_TOKEN,
collateralToken: COLLATERAL_TOKEN,
oracle: ORACLE,
irm: IRM,
lltv: LLTV
});
}

/// @notice The pre-liquidation parameters specific to the PreLiquidation contract.
function preLiquidationParams() external view returns (PreLiquidationParams memory) {
return PreLiquidationParams({
preLltv: PRE_LLTV,
closeFactor: CLOSE_FACTOR,
preLiquidationIncentiveFactor: PRE_LIQUIDATION_INCENTIVE_FACTOR,
preLiquidationOracle: PRE_LIQUIDATION_ORACLE
});
}

/* CONSTRUCTOR */

/// @dev Initializes the PreLiquidation contract.
/// @param morpho The address of the Morpho protocol.
/// @param id The id of the Morpho market on which pre-liquidations will occur.
/// @param _preLiquidationParams The pre-liquidation parameters.
constructor(address morpho, Id id, PreLiquidationParams memory _preLiquidationParams) {
require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket());
MarketParams memory _marketParams = IMorpho(morpho).idToMarketParams(id);
require(_preLiquidationParams.preLltv < _marketParams.lltv, ErrorsLib.PreLltvTooHigh());
require(_preLiquidationParams.closeFactor <= WAD, ErrorsLib.CloseFactorTooHigh());
require(
_preLiquidationParams.preLiquidationIncentiveFactor >= WAD, ErrorsLib.PreLiquidationIncentiveFactorTooLow()
);

MORPHO = IMorpho(morpho);

ID = id;

LOAN_TOKEN = _marketParams.loanToken;
COLLATERAL_TOKEN = _marketParams.collateralToken;
ORACLE = _marketParams.oracle;
IRM = _marketParams.irm;
LLTV = _marketParams.lltv;

PRE_LLTV = _preLiquidationParams.preLltv;
CLOSE_FACTOR = _preLiquidationParams.closeFactor;
PRE_LIQUIDATION_INCENTIVE_FACTOR = _preLiquidationParams.preLiquidationIncentiveFactor;
PRE_LIQUIDATION_ORACLE = _preLiquidationParams.preLiquidationOracle;

ERC20(LOAN_TOKEN).safeApprove(morpho, type(uint256).max);
}

/* PRE-LIQUIDATION */

/// @notice Pre-liquidates the given borrower on the market of this contract and with the parameters of this contract.
/// @dev Either `seizedAssets` or `repaidShares` should be zero.
/// @param borrower The owner of the position.
/// @param seizedAssets The amount of collateral to seize.
/// @param repaidShares The amount of shares to repay.
/// @param data Arbitrary data to pass to the `onPreLiquidate` callback. Pass empty data if not needed.
function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external {
require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput());

MORPHO.accrueInterest(marketParams());

Market memory market = MORPHO.market(ID);
Position memory position = MORPHO.position(ID, borrower);

uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price();
uint256 borrowed = uint256(position.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares);
uint256 borrowThreshold =
uint256(position.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(PRE_LLTV);

require(borrowed > borrowThreshold, ErrorsLib.NotPreLiquidatablePosition());

if (seizedAssets > 0) {
uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);

repaidShares = seizedAssetsQuoted.wDivUp(PRE_LIQUIDATION_INCENTIVE_FACTOR).toSharesUp(
market.totalBorrowAssets, market.totalBorrowShares
);
} else {
seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown(
PRE_LIQUIDATION_INCENTIVE_FACTOR
).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
}

uint256 borrowerShares = position.borrowShares;
uint256 repayableShares = borrowerShares.wMulDown(CLOSE_FACTOR);
require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares));

bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data);
(uint256 repaidAssets,) = MORPHO.repay(marketParams(), 0, repaidShares, borrower, callbackData);

emit EventsLib.PreLiquidate(ID, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets);
}

/// @notice Morpho callback after repay call.
/// @dev During pre-liquidation, Morpho will call the `onMorphoRepay` callback function in `PreLiquidation` using the provided data.
/// This mechanism enables the withdrawal of the position’s collateral before the debt repayment occurs,
/// and can also trigger a pre-liquidator callback. The pre-liquidator callback can be used to swap
/// the seized collateral into the asset being repaid, facilitating liquidation without the need for a flashloan.
function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external {
require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho());
(uint256 seizedAssets, address borrower, address liquidator, bytes memory data) =
abi.decode(callbackData, (uint256, address, address, bytes));

MORPHO.withdrawCollateral(marketParams(), seizedAssets, borrower, liquidator);

if (data.length > 0) {
IPreLiquidationCallback(liquidator).onPreLiquidate(repaidAssets, data);
}

ERC20(LOAN_TOKEN).safeTransferFrom(liquidator, address(this), repaidAssets);
}
}
47 changes: 47 additions & 0 deletions src/PreLiquidationFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;

import {IMorpho, Id} from "../lib/morpho-blue/src/interfaces/IMorpho.sol";
import {PreLiquidation} from "./PreLiquidation.sol";
import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidation.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol";

/// @title PreLiquidationFactory
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice The Fixed LIF, Fixed CF pre-liquidation factory contract for Morpho.
contract PreLiquidationFactory is IPreLiquidationFactory {
/* IMMUTABLE */

/// @notice The address of the Morpho contract.
IMorpho public immutable MORPHO;

/* CONSTRUCTOR */

/// @param morpho The address of the Morpho contract.
constructor(address morpho) {
require(morpho != address(0), ErrorsLib.ZeroAddress());

MORPHO = IMorpho(morpho);
}

/* EXTERNAL */

/// @notice Creates a PreLiquidation contract.
/// @param id The Morpho market for PreLiquidations.
/// @param preLiquidationParams The PreLiquidation params for the PreLiquidation contract.
/// @dev Warning: This function will revert without data if the pre-liquidation already exists.
function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams)
external
returns (IPreLiquidation)
{
IPreLiquidation preLiquidation =
IPreLiquidation(address(new PreLiquidation{salt: 0}(address(MORPHO), id, preLiquidationParams)));

emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams);

return preLiquidation;
}
}
28 changes: 28 additions & 0 deletions src/interfaces/IPreLiquidation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >= 0.5.0;

import {Id, IMorpho, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol";

/// @notice The pre-liquidation parameters are:
/// - preLltv, the maximum LTV of a position before allowing pre-liquidation.
/// - closeFactor, the maximum proportion of debt that can be pre-liquidated at once.
/// - preLiquidationIncentiveFactor, the factor used to multiply repaid debt value to get the seized collateral value in a pre-liquidation.
/// - preLiquidationOracle, the oracle used to assess whether or not a position can be preliquidated.
struct PreLiquidationParams {
uint256 preLltv;
uint256 closeFactor;
uint256 preLiquidationIncentiveFactor;
address preLiquidationOracle;
}

interface IPreLiquidation {
function MORPHO() external view returns (IMorpho);

function ID() external view returns (Id);

function marketParams() external returns (MarketParams memory);

function preLiquidationParams() external view returns (PreLiquidationParams memory);

function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external;
}
12 changes: 12 additions & 0 deletions src/interfaces/IPreLiquidationCallback.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >= 0.5.0;

/// @title IPreLiquidationCallback
/// @notice Interface that "pre-liquidators" willing to use the pre-liquidation callback must implement.
interface IPreLiquidationCallback {
/// @notice Callback called when a pre-liquidation occurs.
/// @dev The callback is called only if data is not empty.
/// @param repaidAssets The amount of repaid assets.
/// @param data Arbitrary data passed to the `preLiquidate` function.
function onPreLiquidate(uint256 repaidAssets, bytes calldata data) external;
}
Loading

0 comments on commit 9ae4f40

Please sign in to comment.