-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from morpho-org/feat/licence
Fixed LI-CF PreLiquidation
- Loading branch information
Showing
25 changed files
with
829 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule forge-std
updated
5 files
+4 −0 | src/StdChains.sol | |
+2 −2 | src/StdCheats.sol | |
+17 −1 | src/Vm.sol | |
+2 −0 | test/StdChains.t.sol | |
+2 −2 | test/Vm.t.sol |
Submodule morpho-blue
added at
044840
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.